home *** CD-ROM | disk | FTP | other *** search
/ Programmer Plus 2007 / Programmer-Plus-2007.iso / Programming / Microsoft Plateform / Visual Basic 5.0 / Msvb50.ace / msvb50 / MSVB50 / VB / SETUPKIT / SETUP1 / SETUP1.BAS < prev    next >
Encoding:
BASIC Source File  |  1997-01-23  |  199.2 KB  |  5,588 lines

  1. Attribute VB_Name = "basSetup1"
  2. Option Explicit
  3. Option Compare Text
  4.  
  5. '
  6. ' Global Constants
  7. '
  8.  
  9. 'Return values for setup toolkit functions
  10. Global Const gintRET_CONT% = 1
  11. Global Const gintRET_CANCEL% = 2
  12. Global Const gintRET_EXIT% = 3
  13. Global Const gintRET_ABORT% = 4
  14. Global Const gintRET_FATAL% = 5
  15. Global Const gintRET_FINISHEDSUCCESS% = 6 'Used only as parameter to ExitSetup at end of successful install
  16.  
  17. 'Error levels for GetAppRemovalCmdLine()
  18. Global Const APPREMERR_NONE = 0 'no error
  19. Global Const APPREMERR_FATAL = 1 'fatal error
  20. Global Const APPREMERR_NONFATAL = 2 'non-fatal error, user chose to abort
  21. Global Const APPREMERR_USERCANCEL = 3 'user chose to cancel (no error)
  22.  
  23. 'Flag for Path Dialog specifying Source or Dest directory needed
  24. Global Const gstrDIR_SRC$ = "S"
  25. Global Const gstrDIR_DEST$ = "D"
  26.  
  27. 'Beginning of lines in [Files], [Bootstrap], and [Licenses] sections of SETUP.LST
  28. Global Const gstrINI_FILE$ = "File"
  29. Global Const gstrINI_REMOTE$ = "Remote"
  30. Public Const gstrINI_LICENSE$ = "License"
  31. '
  32. ' Command line constants
  33. '
  34. Public Const gstrSILENTSWITCH = "s"
  35. Public Const gstrSMSSWITCH = "q"
  36. '
  37. 'Type Definitions
  38. '
  39. Type FILEINFO                                               ' Setup information file line format
  40.     intDiskNum As Integer                                   ' disk number
  41.     fSplit As Integer                                       ' split flag
  42.     strSrcName As String                                    ' name of source file
  43.     strDestName As String                                   ' name of destination file
  44.     strDestDir As String                                    ' destination directory
  45.     strRegister As String                                   ' registration info
  46.     fShared As Boolean                                      ' whether the file is shared or private
  47.     fSystem As Boolean                                      ' whether the file is a system file (i.e. should be installed but never removed)
  48.     varDate As Variant                                      ' file date
  49.     lFileSize As Long                                       ' file size
  50.     sVerInfo As VERINFO                                     ' file version number
  51.     strReserved As String                                   ' Reserved. Leave empty, or error.
  52.     strProgramIconTitle As String                                ' Caption for icon in program group
  53.     strProgramIconCmdLine As String                         ' Command Line for icon in program group
  54. End Type
  55.  
  56. Type DISKINFO                                               ' Disk drive information
  57.     lAvail As Long                                          ' Bytes available on drive
  58.     lReq As Long                                            ' Bytes required for setup
  59.     lMinAlloc As Long                                       ' minimum allocation unit
  60. End Type
  61.  
  62. Type DESTINFO                                               ' save dest dir for certain files
  63.     strAppDir As String
  64.     strAUTMGR32 As String
  65.     strRACMGR32 As String
  66. End Type
  67.  
  68. Type REGINFO                                                ' save registration info for files
  69.     strFilename As String
  70.     strRegister As String
  71.     
  72.     'The following are used only for remote server registration
  73.     strNetworkAddress As String
  74.     strNetworkProtocol As String
  75.     intAuthentication As Integer
  76.     fDCOM As Boolean      ' True if DCOM, otherwise False
  77. End Type
  78.  
  79. '
  80. 'Global Variables
  81. '
  82. Global gstrSETMSG As String
  83. Global gfRetVal As Integer                                  'return value for form based functions
  84. Global gstrAppName As String                                'name of app being installed
  85. Global gstrTitle As String                                  '"setup" name of app being installed
  86. Public gstrDefGroup As String                               'Default name for group -- from setup.lst
  87. Global gstrDestDir As String                                'dest dir for application files
  88. Global gstrAppExe As String                                 'name of app .EXE being installed
  89. Public gstrAppToUninstall As String                         ' Name of app exe/ocx/dll to be uninstalled.  Should be the same as gstrAppExe in most cases.
  90. Global gstrSrcPath As String                                'path of source files
  91. Global gstrSetupInfoFile As String                          'pathname of SETUP.LST file
  92. Global gstrWinDir As String                                 'windows directory
  93. Global gstrWinSysDir As String                              'windows\system directory
  94. Global gsDiskSpace() As DISKINFO                            'disk space for target drives
  95. Global gstrDrivesUsed As String                             'dest drives used by setup
  96. Global glTotalCopied As Long                                'total bytes copied so far
  97. Global gintCurrentDisk As Integer                           'current disk number being installed
  98. Global gsDest As DESTINFO                                   'dest dirs for certain files
  99. Global gstrAppRemovalLog As String                           'name of the app removal logfile
  100. Global gstrAppRemovalEXE As String                           'name of the app removal executable
  101. Global gfAppRemovalFilesMoved As Boolean                     'whether or not the app removal files have been moved to the application directory
  102. Global gfForceUseDefDest As Boolean                         'If set to true, then the user will not be prompted for the destination directory
  103. Global fMainGroupWasCreated As Boolean                     'Whether or not a main folder/group has been created
  104. Public gfRegDAO As Boolean                                 ' If this gets set to true in the code, then
  105.                                                            ' we need to add some registration info for DAO
  106.                                                            ' to the registry.
  107.  
  108.  
  109. '
  110. 'Form/Module Constants
  111. '
  112.  
  113. 'Possible ProgMan actions
  114. Const mintDDE_ITEMADD% = 1                                  'AddProgManItem flag
  115. Const mintDDE_GRPADD% = 2                                   'AddProgManGroup flag
  116.  
  117. 'Special file names
  118. Const mstrFILE_APPREMOVALLOGBASE$ = "ST5UNST"               'Base name of the app removal logfile
  119. Const mstrFILE_APPREMOVALLOGEXT$ = ".LOG"                   'Default extension for the app removal logfile
  120. Const mstrFILE_AUTMGR32 = "AUTMGR32.EXE"
  121. Const mstrFILE_RACMGR32 = "RACMGR32.EXE"
  122. Const mstrFILE_CTL3D32$ = "CTL3D32.DLL"
  123. Const mstrFILE_RICHED32$ = "RICHED32.DLL"
  124.  
  125. 'Name of temporary file used for concatenation of split files
  126. Const mstrCONCATFILE$ = "VB5STTMP.CCT"
  127.  
  128. 'setup information file registration macros
  129. Const mstrDLLSELFREGISTER$ = "$(DLLSELFREGISTER)"
  130. Const mstrEXESELFREGISTER$ = "$(EXESELFREGISTER)"
  131. Const mstrTLBREGISTER$ = "$(TLBREGISTER)"
  132. Const mstrREMOTEREGISTER$ = "$(REMOTE)"
  133. Const mstrVBLREGISTER$ = "$(VBLREGISTER)"  ' Bug 5-8039
  134.  
  135. '
  136. 'Form/Module Variables
  137. '
  138. Private msRegInfo() As REGINFO                                  'files to be registered
  139. Private mlTotalToCopy As Long                                   'total bytes to copy
  140. Private mintConcatFile As Integer                               'handle of dest file for concatenation
  141. Private mlSpaceForConcat As Long                                'extra space required for concatenation
  142. Private mstrConcatDrive As String                               'drive to use for concatenation
  143. Private mstrVerTmpName As String                                'temp file name for VerInstallFile API
  144.  
  145. ' Hkey cache (used for logging purposes)
  146. Private Type HKEY_CACHE
  147.     hKey As Long
  148.     strHkey As String
  149. End Type
  150.  
  151. Private hkeyCache() As HKEY_CACHE
  152.  
  153. ' Registry manipulation API's (32-bit)
  154. Global Const HKEY_CLASSES_ROOT = &H80000000
  155. Global Const HKEY_CURRENT_USER = &H80000001
  156. Global Const HKEY_LOCAL_MACHINE = &H80000002
  157. Global Const HKEY_USERS = &H80000003
  158. Const ERROR_SUCCESS = 0&
  159. Const ERROR_NO_MORE_ITEMS = 259&
  160.  
  161. Const REG_SZ = 1
  162. Const REG_BINARY = 3
  163. Const REG_DWORD = 4
  164.  
  165.  
  166. Declare Function OSRegCloseKey Lib "advapi32" Alias "RegCloseKey" (ByVal hKey As Long) As Long
  167. Declare Function OSRegCreateKey Lib "advapi32" Alias "RegCreateKeyA" (ByVal hKey As Long, ByVal lpszSubKey As String, phkResult As Long) As Long
  168. Declare Function OSRegDeleteKey Lib "advapi32" Alias "RegDeleteKeyA" (ByVal hKey As Long, ByVal lpszSubKey As String) As Long
  169. Declare Function OSRegEnumKey Lib "advapi32" Alias "RegEnumKeyA" (ByVal hKey As Long, ByVal iSubKey As Long, ByVal lpszName As String, ByVal cchName As Long) As Long
  170. Declare Function OSRegOpenKey Lib "advapi32" Alias "RegOpenKeyA" (ByVal hKey As Long, ByVal lpszSubKey As String, phkResult As Long) As Long
  171. Declare Function OSRegQueryValueEx Lib "advapi32" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpszValueName As String, ByVal dwReserved As Long, lpdwType As Long, lpbData As Any, cbData As Long) As Long
  172. Declare Function OSRegSetValueEx Lib "advapi32" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpszValueName As String, ByVal dwReserved As Long, ByVal fdwType As Long, lpbData As Any, ByVal cbData As Long) As Long
  173.  
  174. Declare Function GetCurrentProcessId Lib "kernel32" () As Long
  175.  
  176. '-----------------------------------------------------------
  177. ' SUB: AddPerAppPath
  178. '
  179. ' Adds an application's full pathname and per-app path to the
  180. '   system registry (this is currently only meaningful to
  181. '   Windows 95).
  182. '
  183. ' IN: [strAppExe] - app EXE name, not including path
  184. '     [strAppDir] - full path of EXE, not including filename
  185. '     [strAppPath] - per-app path for this application
  186. '       (semicolon-separated list of directory path names)
  187. '       If this is the empty string (""), no per-app path
  188. '       is registered, but the full pathname of the
  189. '       exe IS still registered.
  190. '
  191. ' OUT:
  192. '   Example registry entries:
  193. '     HKEY_LOCAL_MACHINE\[strPathsBaseKeyName]\MyApp.Exe
  194. '       [Default]=C:\Program Files\MyApp\MyApp.Exe
  195. '       [Path]=C:\Program Files\MyApp;C:\Program Files\MyApp\System
  196. '
  197. '-----------------------------------------------------------
  198. '
  199. Sub AddPerAppPath(ByVal strAppExe As String, ByVal strAppDir As String, ByVal strPerAppPath As String)
  200.     If Not TreatAsWin95() Then
  201.         Exit Sub
  202.     End If
  203.     
  204.     Dim strPathsBaseKeyName As String
  205.     Const strAppPaths$ = "App Paths"
  206.     Const strAppPathKeyName = "Path"
  207.     Dim fOk As Boolean
  208.     Dim hKey As Long
  209.     
  210.     AddDirSep strAppDir
  211.     
  212.     ' Create the new key, whose name is based on the app's name
  213.    If Not RegCreateKey(HKEY_LOCAL_MACHINE, RegPathWinCurrentVersion(), strAppPaths & gstrSEP_DIR & strAppExe, hKey) Then
  214.         GoTo Err
  215.     End If
  216.     
  217.     fOk = True
  218.     
  219.     ' Default value indicates full EXE pathname
  220.     fOk = fOk And RegSetStringValue(hKey, "", strAppDir & strAppExe)
  221.     
  222.     ' [Path] value indicates the per-app path
  223.     If strPerAppPath <> "" Then
  224.         fOk = fOk And RegSetStringValue(hKey, strAppPathKeyName, strPerAppPath)
  225.     End If
  226.     
  227.     If Not fOk Then
  228.         GoTo Err
  229.     End If
  230.     
  231.     RegCloseKey hKey
  232.     
  233.     Exit Sub
  234.     
  235. Err:
  236.     MsgError ResolveResString(resERR_REG), vbExclamation Or vbOKOnly, gstrTitle
  237.     '
  238.     ' If we are running an SMS install, we can't continue.
  239.     '
  240.     If gfSMS Then
  241.         ExitSetup frmSetup1, gintRET_FATAL
  242.     End If
  243. End Sub
  244.  
  245. '-----------------------------------------------------------
  246. ' FUNCTION: AddQuotesToFN
  247. '
  248. ' Given a pathname (directory and/or filename), returns
  249. '   that pathname surrounded by double quotes if the
  250. '   path contains spaces or commas.  This is required for
  251. '   setting up an icon correctly, since otherwise such paths
  252. '   would be interpreted as a pathname plus arguments.
  253. '-----------------------------------------------------------
  254. '
  255. Function AddQuotesToFN(ByVal strFilename) As String
  256.     If InStr(strFilename, " ") Or InStr(strFilename, ",") Then
  257.         AddQuotesToFN = """" & strFilename & """"
  258.     Else
  259.         AddQuotesToFN = strFilename
  260.     End If
  261. End Function
  262.  
  263. '-----------------------------------------------------------
  264. ' SUB: CalcDiskSpace
  265. '
  266. ' Calculates disk space required for installing the files
  267. ' listed in the specified section of the setup information
  268. ' file (SETUP.LST)
  269. '-----------------------------------------------------------
  270. '
  271. Sub CalcDiskSpace(ByVal strSection As String)
  272.     Static fSplitFile As Integer
  273.     Static lDestFileSpace As Long
  274.  
  275.     Dim intIdx As Integer
  276.     Dim intDrvIdx As Integer
  277.     Dim sFile As FILEINFO
  278.     Dim strDrive As String
  279.     Dim lThisFileSpace As Long
  280.  
  281.     intIdx = 1
  282.  
  283.     On Error GoTo CalcDSError
  284.  
  285.     '
  286.     'For each file in the specified section, read info from the setup info file
  287.     '
  288.     Do While ReadSetupFileLine(strSection, intIdx, sFile) = True
  289.         '
  290.         'if the file isn't split or if this is the first section of a split file
  291.         '
  292.         If sFile.strDestDir <> gstrNULL Then
  293.             fSplitFile = sFile.fSplit
  294.  
  295.             '
  296.             'Get the dest drive used for this file.  If this is the first file using
  297.             'the drive for a destination, add the drive to the drives used 'table',
  298.             'allocate an array element for the holding the drive info, and get
  299.             'available disk space and minimum allocation unit
  300.             '
  301.             strDrive = Left$(sFile.strDestDir, 1)
  302.         
  303.             intDrvIdx = InStr(gstrDrivesUsed, strDrive)
  304.             If intDrvIdx = 0 Then
  305.                 gstrDrivesUsed = gstrDrivesUsed & strDrive
  306.                 intDrvIdx = Len(gstrDrivesUsed)
  307.  
  308.                 ReDim Preserve gsDiskSpace(intDrvIdx)
  309.                 gsDiskSpace(intDrvIdx).lAvail = GetDiskSpaceFree(strDrive)
  310.  
  311.                 gsDiskSpace(intDrvIdx).lMinAlloc = GetDrivesAllocUnit(strDrive)
  312.             End If
  313.  
  314.             '
  315.             'Calculate size of the dest final (file size + minimum allocation for drive)
  316.             '
  317.             lThisFileSpace = CalcFinalSize(sFile.lFileSize, strDrive)
  318.             mlTotalToCopy = mlTotalToCopy + lThisFileSpace
  319.  
  320.             '
  321.             'If the file already exists, then if we copy it at all, we'll be
  322.             'replacing it.  So, we get the size of the existing dest file so
  323.             'that we can subtract it from the amount needed later.
  324.             '
  325.             If FileExists(sFile.strDestDir & sFile.strDestName) Then
  326.                 lDestFileSpace = FileLen(sFile.strDestDir & sFile.strDestName)
  327.             Else
  328.                 lDestFileSpace = 0
  329.             End If
  330.         End If
  331.  
  332.         '
  333.         'If file not split, or if the last section of a split file
  334.         '
  335.         If sFile.fSplit = False Then
  336.             '
  337.             'If this is the last section of a split file, then if it's the *largest*
  338.             'split file, set the extra space needed for concatenation to this size
  339.             '
  340.             If fSplitFile = True And lThisFileSpace > mlSpaceForConcat Then
  341.                 mlSpaceForConcat = lThisFileSpace
  342.             End If
  343.  
  344.             '
  345.             'Subtract size of existing dest file, if applicable and then accumulate
  346.             'space required
  347.             '
  348.             lThisFileSpace = lThisFileSpace - lDestFileSpace
  349.             If lThisFileSpace < 0 Then
  350.                 lThisFileSpace = 0
  351.             End If
  352.  
  353.             gsDiskSpace(intDrvIdx).lReq = gsDiskSpace(intDrvIdx).lReq + lThisFileSpace
  354.         End If
  355.  
  356.         intIdx = intIdx + 1
  357.     Loop
  358.  
  359.     Exit Sub
  360.  
  361. CalcDSError:
  362.     MsgError Error$ & LS$ & ResolveResString(resCALCSPACE), MB_ICONSTOP, gstrSETMSG
  363.     ExitSetup frmMessage, gintRET_FATAL
  364. End Sub
  365.  
  366. '-----------------------------------------------------------
  367. ' SUB: CalcFinalSize
  368. '
  369. ' Computes the space required for a file of the size
  370. ' specified on the given dest path.  This includes the
  371. ' file size plus a padding to ensure that the final size
  372. ' is a multiple of the minimum allocation unit for the
  373. ' dest drive
  374. '-----------------------------------------------------------
  375. '
  376. Function CalcFinalSize(lBaseFileSize As Long, strDestPath As String) As Long
  377.     Dim lMinAlloc As Long
  378.     Dim intPadSize As Long
  379.  
  380.     lMinAlloc = gsDiskSpace(InStr(gstrDrivesUsed, Left$(strDestPath, 1))).lMinAlloc
  381.     intPadSize = lMinAlloc - (lBaseFileSize Mod lMinAlloc)
  382.     If intPadSize = lMinAlloc Then
  383.         intPadSize = 0
  384.     End If
  385.  
  386.     CalcFinalSize = lBaseFileSize + intPadSize
  387. End Function
  388.  
  389. '-----------------------------------------------------------
  390. ' SUB: CenterForm
  391. '
  392. ' Centers the passed form just above center on the screen
  393. '-----------------------------------------------------------
  394. '
  395. Sub CenterForm(frm As Form)
  396.     SetMousePtr gintMOUSE_HOURGLASS
  397.  
  398.     frm.Top = (Screen.Height * 0.85) \ 2 - frm.Height \ 2
  399.     frm.Left = Screen.Width \ 2 - frm.Width \ 2
  400.  
  401.     SetMousePtr gintMOUSE_DEFAULT
  402. End Sub
  403.  
  404. '-----------------------------------------------------------
  405. ' FUNCTION: CheckDiskSpace
  406. '
  407. ' Reads from the space required array generated by calling
  408. ' the 'CalcDiskSpace' function and determines whether there
  409. ' is sufficient free space on all of the drives used for
  410. ' installation
  411. '
  412. ' Returns: True if there is enough space, False otherwise
  413. '-----------------------------------------------------------
  414. '
  415. Function CheckDiskSpace() As Integer
  416.     Static fDontAskOnSpaceErr As Integer
  417.  
  418.     Dim intIdx As Integer
  419.     Dim intTmpDrvIdx As Integer
  420.     Dim lDiskSpaceLeft As Long
  421.     Dim lMostSpaceLeft As Long
  422.                                              
  423.     '
  424.     'Default to True (enough space on all drives)
  425.     '
  426.     CheckDiskSpace = True
  427.  
  428.     '
  429.     'For each drive that is the destination for one or more files, compare
  430.     'the space available to the space required.
  431.     '
  432.     For intIdx = 1 To Len(gstrDrivesUsed)
  433.         lDiskSpaceLeft = gsDiskSpace(intIdx).lAvail - gsDiskSpace(intIdx).lReq
  434.         If lDiskSpaceLeft < 0 Then
  435.             GoSub CheckDSAskSpace
  436.         Else
  437.             '
  438.             'If no "TMP" drive was found, or if the "TMP" drive wasn't ready,
  439.             'save the index of the drive and the amount of space on the drive
  440.             'which will have the most free space.  If no "TMP" drive was
  441.             'found in InitDiskInfo(), then this drive will be used as a
  442.             'temporary drive for concatenating split files
  443.             '
  444.             If mstrConcatDrive = gstrNULL Then
  445.                 If lDiskSpaceLeft > lMostSpaceLeft Then
  446.                     lMostSpaceLeft = lDiskSpaceLeft
  447.                     intTmpDrvIdx = intIdx
  448.                 End If
  449.             Else
  450.                 '
  451.                 '"TMP" drive was specified, so we'll use that
  452.                 '
  453.                 If Left$(mstrConcatDrive, 1) = Mid$(gstrDrivesUsed, intIdx, 1) Then
  454.                     intTmpDrvIdx = intIdx
  455.                 End If
  456.             End If
  457.         End If
  458.     Next
  459.  
  460.     '
  461.     'If at least one drive was specified as a destination (if there was at least
  462.     'one CalcDiskSpace call in Form_Load of SETUP1.FRM), then subtract the extra
  463.     'space needed for concatenation from either:
  464.     '   The "TMP" drive if available  - OR -
  465.     '   The drive with the most space remaining
  466.     '
  467.     If intTmpDrvIdx > 0 Then
  468.         gsDiskSpace(intTmpDrvIdx).lReq = gsDiskSpace(intTmpDrvIdx).lReq + mlSpaceForConcat
  469.         If gsDiskSpace(intTmpDrvIdx).lAvail < gsDiskSpace(intTmpDrvIdx).lReq Then
  470.             GoSub CheckDSAskSpace
  471.         End If
  472.  
  473.         '
  474.         'If a "TMP" drive was found, we use it regardless, otherwise we use the drive
  475.         'with the most free space
  476.         '
  477.         If mstrConcatDrive = gstrNULL Then
  478.             mstrConcatDrive = Mid$(gstrDrivesUsed, intTmpDrvIdx, 1) & gstrCOLON & gstrSEP_DIR
  479.             AddDirSep mstrConcatDrive
  480.         End If
  481.     End If
  482.  
  483.     Exit Function
  484.  
  485. CheckDSAskSpace:
  486.     '
  487.     'if the user hasn't been prompted before in the event of not enough free space,
  488.     'then display table of drive space and allow them to (basically) abort, retry,
  489.     'or ignore.
  490.     '
  491.     If fDontAskOnSpaceErr = False Then
  492.         If gfNoUserInput Then
  493.             If gfSilent = True Then
  494.                 LogSilentMsg ResolveResString(resLBLNOSPACE)
  495.             End If
  496.             If gfSMS = True Then
  497.                 LogSMSMsg ResolveResString(resLBLNOSPACE)
  498.             End If
  499.             ExitSetup frmSetup1, gintRET_FATAL
  500.         Else
  501.             frmDskSpace.Show 1
  502.         End If
  503.         
  504.         If gfRetVal <> gintRET_CONT Then
  505.             CheckDiskSpace = False
  506.             Exit Function
  507.         Else
  508.             fDontAskOnSpaceErr = True
  509.         End If
  510.     End If
  511.  
  512.     Return
  513. End Function
  514.  
  515. '-----------------------------------------------------------
  516. ' FUNCTION: CheckDrive
  517. '
  518. ' Check to see if the specified drive is ready to be read
  519. ' from.  In the case of a drive that holds removable media,
  520. ' this would mean that formatted media was in the drive and
  521. ' that the drive door was closed.
  522. '
  523. ' IN: [strDrive] - drive to check
  524. '     [strCaption] - caption if the drive isn't ready
  525. '
  526. ' Returns: True if the drive is ready, False otherwise
  527. '-----------------------------------------------------------
  528. '
  529. Function CheckDrive(ByVal strDrive As String, ByVal strCaption As String) As Integer
  530.     Dim strDir As String
  531.     Dim strMsg As String
  532.     Dim fIsUNC As Boolean
  533.  
  534.     On Error Resume Next
  535.  
  536.     SetMousePtr gintMOUSE_HOURGLASS
  537.  
  538.     Do
  539.         Err = 0
  540.         fIsUNC = False
  541.         '
  542.         'Attempt to read the current directory of the specified drive.  If
  543.         'an error occurs, we assume that the drive is not ready
  544.         '
  545.         If IsUNCName(strDrive) Then
  546.             fIsUNC = True
  547.             strDir = Dir$(GetUNCShareName(strDrive))
  548.         Else
  549.             strDir = Dir$(Left$(strDrive, 2))
  550.         End If
  551.  
  552.         If Err > 0 Then
  553.             If fIsUNC Then
  554.                 strMsg = Error$ & LS$ & ResolveResString(resCANTREADUNC, "|1", strDrive) & LS$ & ResolveResString(resCHECKUNC)
  555.             Else
  556.                 strMsg = Error$ & LS$ & ResolveResString(resDRVREAD) & strDrive & LS$ & ResolveResString(resDRVCHK)
  557.             End If
  558.             If MsgError(strMsg, MB_ICONEXCLAMATION Or MB_RETRYCANCEL, strCaption) = IDCANCEL Then
  559.                 CheckDrive = False
  560.                 Err = 0
  561.             End If
  562.         Else
  563.             CheckDrive = True
  564.         End If
  565.         
  566.         If Err And gfNoUserInput = True Then
  567.             ExitSetup frmSetup1, gintRET_FATAL
  568.         End If
  569.     Loop While Err
  570.  
  571.     SetMousePtr gintMOUSE_DEFAULT
  572. End Function
  573.  
  574. '-----------------------------------------------------------
  575. ' FUNCTION: CheckOverwritePrivateFile
  576. '
  577. ' Checks if a private file that we are about to install
  578. ' already exists in the destination directory.  If it
  579. ' does, there will be problems if the user ever tries to
  580. ' remove either application, so warn the user and suggest
  581. ' selecting a different destination directory.
  582. '
  583. ' IN: [strFN] - Full path of the private file that is
  584. '               about to be installed.
  585. '
  586. '-----------------------------------------------------------
  587. '
  588. Sub CheckOverwritePrivateFile(ByVal strFN As String)
  589.     Static fIgnoreOverwrite As Boolean
  590.     
  591.     If fIgnoreOverwrite Then
  592.         'If the users once chooses to ignore this warning,
  593.         'we will not bring it up again.
  594.         Exit Sub
  595.     End If
  596.     
  597.     If FileExists(strFN) Then
  598.         Do
  599.             Select Case MsgError(ResolveResString(resOVERWRITEPRIVATE) & LS$ & ResolveResString(resCANCELSETUP), vbYesNo Or vbDefaultButton1 Or vbExclamation, gstrTitle)
  600.                 Case vbYes
  601.                     'The user chose to cancel.  (This is best.)
  602.                     gfDontLogSMS = True  ' Don't log this message if SMS because we already logged the previous one and we can only use 255 characters.
  603.                     MsgError ResolveResString(resCHOOSENEWDEST), vbOKOnly, gstrTitle
  604.                     ExitSetup frmCopy, gintRET_FATAL
  605.                 Case Else
  606.                     'One more level of warning to let them know that we highly
  607.                     '  recommend cancelling setup at this point
  608.                     Select Case MsgError(ResolveResString(resOVERWRITEPRIVATE2) & LS$ & ResolveResString(resVERIFYCONTINUE), vbYesNo Or vbDefaultButton2 Or vbExclamation, gstrTitle)
  609.                         Case vbNo
  610.                             'User chose "no, don't continue"
  611.                             'Repeat the first-level warning
  612.                         Case Else
  613.                             'They decided to continue anyway
  614.                             fIgnoreOverwrite = True
  615.                             Exit Do
  616.                         'End Case
  617.                     End Select
  618.                 'End Case
  619.             End Select
  620.         Loop
  621.     End If
  622. End Sub
  623.  
  624. '-----------------------------------------------------------
  625. ' FUNCTION: ConcatSplitFile
  626. '
  627. ' Reads and appends the source file passed in onto the
  628. ' previously opened destination file specified by
  629. ' mintConcatFile.  mintConcatFile should be opened
  630. ' by calling OpenConcatFile() before calling this function.
  631. '
  632. ' IN: [strSrcName] - Source file to append to destination
  633. '
  634. ' Returns: True if copy was successful, IDIGNORE if user
  635. '          elects to ignore a reported copy error
  636. '-----------------------------------------------------------
  637. '
  638. Function ConcatSplitFile(ByVal strSrcName As String) As Integer
  639.     Const lMAXCOPYBUF& = 64512
  640.     Const lMINCOPYBUFSIZE& = 4096
  641.     Const intOPEN% = 1
  642.     Const intGET% = 2
  643.     Const intPUT% = 3
  644.     Const intMEMFAIL% = 4
  645.  
  646.     Dim intSrcFile As Integer
  647.     Dim intStatus As Integer
  648.     Dim lBytesLeftToWrite As Long
  649.     Dim lBytesThisTime As Long
  650.     Dim byteFileBuf() As Byte 'This must be byte rather than String, so no Unicode conversion takes place
  651.     Dim strMsg As String
  652.  
  653.     On Error GoTo CSFError
  654.     
  655.     '
  656.     'Ensure that the specified source file is available
  657.     '
  658.     If DetectFile(strSrcName) = IDIGNORE Then
  659.         ConcatSplitFile = IDIGNORE
  660.         Exit Function
  661.     End If
  662.  
  663.     lBytesLeftToWrite = FileLen(strSrcName)
  664.  
  665.     '
  666.     'For error reporting, flag that we're attempting to open the file now
  667.     '
  668.     intStatus = intOPEN
  669.  
  670.     '
  671.     'Open the source file for reading now
  672.     '
  673.     intSrcFile = FreeFile
  674.     Open strSrcName For Binary Access Read As intSrcFile
  675.  
  676.     '
  677.     'Initially, we'll try to copy lMAXCOPYBUF bytes at a time.  If our attempt
  678.     'to allocate a copy buffer (Space$(...)) fails, the error handling logic
  679.     'will cause the buffer size to be halved and another allocation attempt to
  680.     'be made.
  681.     '
  682.     lBytesThisTime = lMAXCOPYBUF
  683.     ReDim byteFileBuf(1 To lBytesThisTime) As Byte
  684.  
  685.     While lBytesLeftToWrite <> 0
  686.         '
  687.         'while source file hasn't been read, if the number of bytes left is bigger than
  688.         'the buffer size, reduce the buffer size
  689.         '
  690.         If lBytesThisTime > lBytesLeftToWrite Then
  691.             lBytesThisTime = lBytesLeftToWrite
  692.             ReDim byteFileBuf(1 To lBytesThisTime) As Byte
  693.         End If
  694.         
  695.         '
  696.         'Set operation status and Get from the source file and Put to the dest file
  697.         '
  698.         intStatus = intGET
  699.         Get intSrcFile, , byteFileBuf
  700.  
  701.         intStatus = intPUT
  702.         Put mintConcatFile, , byteFileBuf
  703.  
  704.         lBytesLeftToWrite = lBytesLeftToWrite - lBytesThisTime
  705.     Wend
  706.  
  707.     ConcatSplitFile = True
  708.     GoTo CSFCleanup
  709.  
  710. CSFError:
  711.     If Err = 14 Then    'Out of String Space
  712.         lBytesThisTime = lBytesThisTime \ 2
  713.         If lBytesThisTime >= lMINCOPYBUFSIZE Then
  714.             Resume
  715.         Else
  716.             intStatus = intMEMFAIL
  717.         End If
  718.     End If
  719.  
  720.     strMsg = LF$ & strSrcName
  721.  
  722.     Select Case intStatus
  723.         Case intOPEN
  724.             strMsg = ResolveResString(resCANTOPEN) & strMsg
  725.         Case intGET
  726.             strMsg = ResolveResString(resCANTREAD) & strMsg
  727.         Case intPUT
  728.             strMsg = ResolveResString(resCANTWRITE) & strMsg & LS$ & ResolveResString(resCHKSPACE)
  729.         Case intMEMFAIL
  730.             strMsg = ResolveResString(resOUTOFMEMORY) & strMsg
  731.         'End Case
  732.     End Select
  733.  
  734.     Select Case MsgError(Error$ & LS$ & strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or IIf(gfNoUserInput, vbDefaultButton1, MB_DEFBUTTON2), gstrSETMSG)
  735.         Case IDABORT
  736.             ExitSetup frmCopy, gintRET_ABORT
  737.         Case IDIGNORE
  738.             ConcatSplitFile = IDIGNORE
  739.         Case IDRETRY
  740.             Resume
  741.         'End Case
  742.     End Select
  743.  
  744. CSFCleanup:
  745.     Close intSrcFile
  746.     Err = 0
  747.     Exit Function
  748. End Function
  749.  
  750. '-----------------------------------------------------------
  751. ' FUNCTION: CopyFile
  752. '
  753. ' Uses the Windows VerInstallFile API to copy a file from
  754. ' the specified source location/name to the destination
  755. ' location/name.  Split files should be combined via the
  756. ' '...Concat...' file routines before calling this
  757. ' function.
  758. ' If the file is successfully updated and the file is a
  759. ' shared file (fShared = True), then the
  760. ' files reference count is updated (32-bits only)
  761. '
  762. ' IN: [strSrcDir] - directory where source file is located
  763. '     [strDestDir] - destination directory for file
  764. '     [strSrcName] - name of source file
  765. '     [strDestName] - name of destination file
  766. '
  767. ' PRECONDITION: NewAction() must have already been called
  768. '               for this file copy (of type either
  769. '               gstrKEY_SHAREDFILE or gstrKEY_PRIVATE --
  770. '               see CopySection for an example of how
  771. '               this works).  See NewAction() and related
  772. '               functions in LOGGING.BAS for comments on
  773. '               using the logging function.
  774. '               Either CommitAction() or AbortAction() will
  775. '               allows be called by this procedure, and
  776. '               should not be done by the caller.
  777. '
  778. ' Returns: True if copy was successful, False otherwise
  779. '
  780. ' POSTCONDITION: The current action will be either committed or
  781. '                aborted.
  782. '-----------------------------------------------------------
  783. '
  784. Function CopyFile(ByVal strSrcDir As String, ByVal strDestDir As String, ByVal strSrcName As String, ByVal strDestName As String, ByVal fShared As Boolean, ByVal fSystem As Boolean) As Boolean
  785.     Const intUNKNOWN% = 0
  786.     Const intCOPIED% = 1
  787.     Const intNOCOPY% = 2
  788.     Const intFILEUPTODATE% = 3
  789.  
  790.     '
  791.     'VerInstallFile() Flags
  792.     '
  793.     Const VIFF_FORCEINSTALL% = &H1
  794.     Const VIF_TEMPFILE& = &H1
  795.     Const VIF_SRCOLD& = &H4
  796.     Const VIF_DIFFLANG& = &H8
  797.     Const VIF_DIFFCODEPG& = &H10
  798.     Const VIF_DIFFTYPE& = &H20
  799.     Const VIF_WRITEPROT& = &H40
  800.     Const VIF_FILEINUSE& = &H80
  801.     Const VIF_OUTOFSPACE& = &H100
  802.     Const VIF_ACCESSVIOLATION& = &H200
  803.     Const VIF_SHARINGVIOLATION = &H400
  804.     Const VIF_CANNOTCREATE = &H800
  805.     Const VIF_CANNOTDELETE = &H1000
  806.     Const VIF_CANNOTRENAME = &H2000
  807.     Const VIF_OUTOFMEMORY = &H8000&
  808.     Const VIF_CANNOTREADSRC = &H10000
  809.     Const VIF_CANNOTREADDST = &H20000
  810.     Const VIF_BUFFTOOSMALL = &H40000
  811.  
  812.     Static fIgnoreWarn As Integer             'user warned about ignoring error?
  813.  
  814.     Dim strMsg As String
  815.     Dim lRC As Long
  816.     Dim lpTmpNameLen As Long
  817.     Dim intFlags As Integer
  818.     Dim intRESULT As Integer
  819.     Dim fFileAlreadyExisted
  820.  
  821.     On Error Resume Next
  822.  
  823.     CopyFile = False
  824.  
  825.     '
  826.     'Ensure that the source file is available for copying
  827.     '
  828.     If DetectFile(strSrcDir & strSrcName) = IDIGNORE Then
  829.         AbortAction
  830.         Exit Function
  831.     End If
  832.     
  833.     '
  834.     ' Make sure that the Destination path (including path, filename, commandline args, etc.
  835.     ' is not longer than the max allowed.
  836.     '
  837.     If Not fCheckFNLength(strDestDir & strDestName) Then
  838.         AbortAction
  839.         strMsg = ResolveResString(resCANTCOPYPATHTOOLONG) & LS$ & ResolveResString(resCHOOSENEWDEST) & LS$ & strDestDir & strDestName
  840.         Call MsgError(strMsg, vbOKOnly, gstrSETMSG)
  841.         ExitSetup frmCopy, gintRET_FATAL
  842.         Exit Function
  843.     End If
  844.     '
  845.     'Make the destination directory, prompt the user to retry if there is an error
  846.     '
  847.     If Not MakePath(strDestDir) Then
  848.         AbortAction ' Abort file copy
  849.         Exit Function
  850.     End If
  851.  
  852.     '
  853.     'Make sure we have the LFN (long filename) of the destination directory
  854.     '
  855.     strDestDir = GetLongPathName(strDestDir)
  856.     
  857.     '
  858.     'Setup for VerInstallFile call
  859.     '
  860.     lpTmpNameLen = gintMAX_SIZE
  861.     mstrVerTmpName = String$(lpTmpNameLen, 0)
  862.     intFlags = 0
  863.     fFileAlreadyExisted = FileExists(strDestDir & strDestName)
  864.  
  865.     intRESULT = intUNKNOWN
  866.  
  867.     Do While intRESULT = intUNKNOWN
  868.         'VerInstallFile under Windows 95 does not handle
  869.         '  long filenames, so we must give it the short versions
  870.         '  (32-bit only).
  871.         Dim strShortSrcName As String
  872.         Dim strShortDestName As String
  873.         Dim strShortSrcDir As String
  874.         Dim strShortDestDir As String
  875.         
  876.         strShortSrcName = strSrcName
  877.         strShortSrcDir = strSrcDir
  878.         strShortDestName = strDestName
  879.         strShortDestDir = strDestDir
  880.         If Not FileExists(strDestDir & strDestName) Then
  881.             'If the destination file does not already
  882.             '  exist, we create a dummy with the correct
  883.             '  (long) filename so that we can get its
  884.             '  short filename for VerInstallFile.
  885.             Open strDestDir & strDestName For Output Access Write As #1
  886.             Close #1
  887.         End If
  888.     
  889.         On Error GoTo UnexpectedErr
  890.         If Not IsWindowsNT() Then
  891.             'This conversion is not necessary under Windows NT
  892.             strShortSrcDir = GetShortPathName(strSrcDir)
  893.             strShortSrcName = GetFileName(GetShortPathName(strSrcDir & strSrcName))
  894.             strShortDestDir = GetShortPathName(strDestDir)
  895.             strShortDestName = GetFileName(GetShortPathName(strDestDir & strDestName))
  896.         End If
  897.         On Error Resume Next
  898.             
  899.         lRC = VerInstallFile(intFlags, strShortSrcName, strShortDestName, strShortSrcDir, strShortDestDir, 0&, mstrVerTmpName, lpTmpNameLen)
  900.         If Err <> 0 Then
  901.             '
  902.             'If the version or file expansion DLLs couldn't be found, then abort setup
  903.             '
  904.             ExitSetup frmCopy, gintRET_FATAL
  905.         End If
  906.  
  907.         If lRC = 0 Then
  908.             '
  909.             'File was successfully installed, increment reference count if needed
  910.             '
  911.             
  912.             'One more kludge for long filenames: VerInstallFile may have renamed
  913.             'the file to its short version if it went through with the copy.
  914.             'Therefore we simply rename it back to what it should be.
  915.             Name strDestDir & strShortDestName As strDestDir & strDestName
  916.             intRESULT = intCOPIED
  917.         ElseIf lRC And VIF_SRCOLD Then
  918.             '
  919.             'Source file was older, so not copied, the existing version of the file
  920.             'will be used.  Increment reference count if needed
  921.             '
  922.             intRESULT = intFILEUPTODATE
  923.         ElseIf lRC And (VIF_DIFFLANG Or VIF_DIFFCODEPG Or VIF_DIFFTYPE) Then
  924.             '
  925.             'We retry and force installation for these cases.  You can modify the code
  926.             'here to prompt the user about what to do.
  927.             '
  928.             intFlags = VIFF_FORCEINSTALL
  929.         ElseIf lRC And VIF_WRITEPROT Then
  930.             strMsg = ResolveResString(resWRITEPROT)
  931.             GoSub CFMsg
  932.         ElseIf lRC And VIF_FILEINUSE Then
  933.             strMsg = ResolveResString(resINUSE)
  934.             GoSub CFMsg
  935.         ElseIf lRC And VIF_OUTOFSPACE Then
  936.             strMsg = ResolveResString(resOUTOFSPACE) & Left$(strDestDir, 2)
  937.             GoSub CFMsg
  938.         ElseIf lRC And VIF_ACCESSVIOLATION Then
  939.             strMsg = ResolveResString(resACCESSVIOLATION)
  940.             GoSub CFMsg
  941.         ElseIf lRC And VIF_SHARINGVIOLATION Then
  942.             strMsg = ResolveResString(resSHARINGVIOLATION)
  943.             GoSub CFMsg
  944.         ElseIf lRC And VIF_OUTOFMEMORY Then
  945.             strMsg = ResolveResString(resOUTOFMEMORY)
  946.             GoSub CFMsg
  947.         Else
  948.             '
  949.             ' For these cases, we generically report the error and do not install the file
  950.             ' unless this is an SMS install; in which case we abort.
  951.             '
  952.             If lRC And VIF_CANNOTCREATE Then
  953.                 strMsg = ResolveResString(resCANNOTCREATE)
  954.             ElseIf lRC And VIF_CANNOTDELETE Then
  955.                 strMsg = ResolveResString(resCANNOTDELETE)
  956.             ElseIf lRC And VIF_CANNOTRENAME Then
  957.                 strMsg = ResolveResString(resCANNOTRENAME)
  958.             ElseIf lRC And VIF_CANNOTREADSRC Then
  959.                 strMsg = ResolveResString(resCANNOTREADSRC)
  960.             ElseIf lRC And VIF_CANNOTREADDST Then
  961.                 strMsg = ResolveResString(resCANNOTREADDST)
  962.             ElseIf lRC And VIF_BUFFTOOSMALL Then
  963.                 strMsg = ResolveResString(resBUFFTOOSMALL)
  964.             End If
  965.  
  966.             strMsg = strMsg & ResolveResString(resNOINSTALL)
  967.             MsgError strMsg, MB_OK Or MB_ICONEXCLAMATION, gstrTitle
  968.             If gfSMS Then
  969.                 ExitSetup frmSetup1, gintRET_FATAL
  970.             End If
  971.             intRESULT = intNOCOPY
  972.         End If
  973.     Loop
  974.  
  975.     '
  976.     'If there was a temp file left over from VerInstallFile, remove it
  977.     '
  978.     If lRC And VIF_TEMPFILE Then
  979.         Kill mstrVerTmpName
  980.     End If
  981.  
  982.     'Abort or commit the current Action, and do reference counting
  983.     Select Case intRESULT
  984.         Case intNOCOPY
  985.             AbortAction
  986.         Case intCOPIED
  987.             DecideIncrementRefCount strDestDir & strDestName, fShared, fSystem, fFileAlreadyExisted
  988.             AddActionNote ResolveResString(resLOG_FILECOPIED)
  989.             CommitAction
  990.             CopyFile = True
  991.         Case intFILEUPTODATE
  992.             DecideIncrementRefCount strDestDir & strDestName, fShared, fSystem, fFileAlreadyExisted
  993.             AddActionNote ResolveResString(resLOG_FILEUPTODATE)
  994.             CommitAction
  995.             CopyFile = True
  996.         Case Else
  997.             AbortAction ' Defensive - this shouldn't be reached
  998.         'End Case
  999.     End Select
  1000.  
  1001.     Exit Function
  1002.  
  1003. UnexpectedErr:
  1004.     MsgError Error$ & LS$ & ResolveResString(resUNEXPECTED), vbOKOnly Or vbExclamation, gstrTitle
  1005.     ExitSetup frmCopy, gintRET_FATAL
  1006.     
  1007. CFMsg: '(Subroutine)
  1008.     Dim intMsgRet As Integer
  1009.     strMsg = strDestDir & strDestName & LS$ & strMsg
  1010.     intMsgRet = MsgError(strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrTitle)
  1011.     If gfNoUserInput Then intMsgRet = IDABORT
  1012.     Select Case intMsgRet
  1013.         Case IDABORT
  1014.             ExitSetup frmCopy, gintRET_ABORT
  1015.         Case IDIGNORE
  1016.             If fIgnoreWarn = True Then
  1017.                 intRESULT = intNOCOPY
  1018.             Else
  1019.                 fIgnoreWarn = True
  1020.                 strMsg = strMsg & LS$ & ResolveResString(resWARNIGNORE)
  1021.                 If MsgError(strMsg, MB_YESNO Or MB_ICONQUESTION Or MB_DEFBUTTON2, gstrTitle) = IDYES Then
  1022.                     intRESULT = intNOCOPY
  1023.                 Else
  1024.                     'Will retry
  1025.                 End If
  1026.             End If
  1027.         'End Case
  1028.     End Select
  1029.  
  1030.     Return
  1031. End Function
  1032.  
  1033. '-----------------------------------------------------------
  1034. ' SUB: CopySection
  1035. '
  1036. ' Attempts to copy the files that need to be copied from
  1037. ' the named section of the setup info file (SETUP.LST)
  1038. '
  1039. ' IN: [strSection] - name of section to copy files from
  1040. '
  1041. '-----------------------------------------------------------
  1042. '
  1043. Sub CopySection(ByVal strSection As String)
  1044.     Dim intIdx As Integer
  1045.     Dim fSplit As Integer
  1046.     Dim fSrcVer As Integer
  1047.     Dim sFile As FILEINFO
  1048.     Dim strLastFile As String
  1049.     Dim intRC As Integer
  1050.     Dim lThisFileSize As Long
  1051.     Dim strSrcDir As String
  1052.     Dim strDestDir As String
  1053.     Dim strSrcName As String
  1054.     Dim strDestName As String
  1055.     Dim strRegister As String
  1056.     Dim sSrcVerInfo As VERINFO
  1057.     Dim sDestVerInfo As VERINFO
  1058.     Dim fFileWasUpToDate As Boolean
  1059.     Dim strMultDirBaseName As String
  1060.     Dim strMsg As String
  1061.     Dim strDetectPath As String
  1062.     Dim fRemoteReg As Boolean
  1063.  
  1064.     On Error Resume Next
  1065.  
  1066.     strMultDirBaseName = ResolveResString(resCOMMON_MULTDIRBASENAME)
  1067.     intIdx = 1
  1068.  
  1069.     '
  1070.     'For each file in the specified section, read info from the setup info file
  1071.     '
  1072.     Do While ReadSetupFileLine(strSection, intIdx, sFile) = True
  1073.         fFileWasUpToDate = False
  1074.         
  1075.         '
  1076.         'If last result was IGNORE, and if this is an extent of a split file,
  1077.         'then no need to process this chunk of the file either
  1078.         '
  1079.         If intRC = IDIGNORE And sFile.strDestName = strDestName Then
  1080.             GoTo CSContinue
  1081.         End If
  1082.  
  1083.         intRC = 0
  1084.  
  1085.         '
  1086.         ' If a new disk is called for, or if for some reason we can't find the
  1087.         ' source path (user removed the install floppy, for instance) then
  1088.         ' prompt for the next disk.  The PromptForNextDisk function won't
  1089.         ' actually prompt the user unless it determines that the source drive
  1090.         ' contains removeable media or is a network connection.  Also, we don't
  1091.         ' prompt if this is a silent install.  It will fail later on a silent
  1092.         ' install when it can't find the file.
  1093.         '
  1094.         If gfNoUserInput = False And (sFile.intDiskNum <> gintCurrentDisk Or DirExists(gstrSrcPath) = False) Then
  1095.             PromptForNextDisk sFile.intDiskNum, sFile.strSrcName
  1096.         End If
  1097.  
  1098.         strSrcName = sFile.strSrcName
  1099.         '
  1100.         ' The file could exist in either the main source directory or
  1101.         ' in a subdirectory named DISK1, DISK2, etc.  Set the appropriate
  1102.         ' path.  If it's in neither place, it is an error and will be
  1103.         ' handled later.
  1104.         '
  1105.         If FileExists(gstrSrcPath & strSrcName) = True Then
  1106.             strSrcDir = gstrSrcPath
  1107.         ElseIf FileExists(gstrSrcPath & ".." & gstrSEP_DIR & strMultDirBaseName & Format(sFile.intDiskNum) & gstrSEP_DIR & strSrcName) = True Then
  1108.             strSrcDir = ResolveDir(gstrSrcPath & ".." & gstrSEP_DIR & strMultDirBaseName & Format(sFile.intDiskNum) & gstrSEP_DIR, False, False)
  1109.             gstrSrcPath = strSrcDir
  1110.         Else
  1111.             '
  1112.             ' Can't find the file.
  1113.             '
  1114.             If DirExists(gstrSrcPath & strMultDirBaseName & Format(sFile.intDiskNum)) = True Then
  1115.                 strDetectPath = gstrSrcPath & strMultDirBaseName & Format(sFile.intDiskNum)
  1116.             Else
  1117.                 strDetectPath = gstrSrcPath
  1118.             End If
  1119.             strMsg = ResolveResString(resCOMMON_CANTFINDSRCFILE, "|1", strDetectPath & gstrSEP_DIR & strSrcName)
  1120.             MsgError strMsg, vbExclamation Or vbOKOnly, gstrTitle
  1121.             ExitSetup frmCopy, gintRET_FATAL
  1122.         End If
  1123.  
  1124.         '
  1125.         'if the file isn't split, or if this is the first section of a split file
  1126.         '
  1127.         If sFile.strDestDir <> gstrNULL Then
  1128.             fSplit = sFile.fSplit
  1129.  
  1130.             strDestDir = sFile.strDestDir
  1131.             strDestName = sFile.strDestName
  1132.             
  1133.             'We need to go ahead and create the destination directory, or else
  1134.             'GetLongPathName() may fail
  1135.             If Not MakePath(strDestDir) Then
  1136.                 intRC = IDIGNORE
  1137.             End If
  1138.             
  1139.             If intRC <> IDIGNORE Then
  1140.                 Err = 0
  1141.                 strDestDir = GetLongPathName(strDestDir)
  1142.  
  1143.                 frmCopy.lblDestFile.Caption = strDestDir & sFile.strDestName
  1144.                 frmCopy.lblDestFile.Refresh
  1145.  
  1146.                 If UCase(strDestName) = gstrFILE_AXDIST Then
  1147.                     '
  1148.                     ' AXDIST.EXE is installed temporarily.  We'll be
  1149.                     ' deleting it at the end of setup.  Set gfAXDist = True
  1150.                     ' so we know we need to delete it later.
  1151.                     '
  1152.                     NewAction gstrKEY_TEMPFILE, """" & strDestDir & strDestName & """"
  1153.                     gfAXDist = True
  1154.                     gstrAXDISTInstallPath = strDestDir & strDestName
  1155.                 ElseIf UCase(strDestName) = gstrFILE_WINT351 Then
  1156.                     '
  1157.                     ' WINt351.EXE is installed temporarily.  We'll be
  1158.                     ' deleting it at the end of setup.  Set WINt351 = True
  1159.                     ' so we know we need to delete it later.  (Note, this file
  1160.                     ' is only installed if the target is nt3.51.  This is dealt
  1161.                     ' with below in this same routine.  Rick Andrews)
  1162.                     '
  1163.                     NewAction gstrKEY_TEMPFILE, """" & strDestDir & strDestName & """"
  1164.                     gfWINt351 = True
  1165.                     gstrWINt351InstallPath = strDestDir & strDestName
  1166.                 ElseIf sFile.fShared Then
  1167.                     NewAction gstrKEY_SHAREDFILE, """" & strDestDir & strDestName & """"
  1168.                 ElseIf sFile.fSystem Then
  1169.                     NewAction gstrKEY_SYSTEMFILE, """" & strDestDir & strDestName & """"
  1170.                 Else
  1171.                     NewAction gstrKEY_PRIVATEFILE, """" & strDestDir & strDestName & """"
  1172.                     CheckOverwritePrivateFile strDestDir & strDestName
  1173.                 End If
  1174.             End If
  1175.             
  1176.             '
  1177.             'If the file info just read from SETUP.LST is the application .EXE
  1178.             '(i.e.; it's the value of the AppExe Key in the [Setup] section,
  1179.             'then save it's full pathname for later use
  1180.             '
  1181.             If strDestName = gstrAppExe Then
  1182.                 '
  1183.                 'Used for creating a program manager icon in Form_Load of SETUP1.FRM
  1184.                 'and for registering the per-app path
  1185.                 '
  1186.                 gsDest.strAppDir = strDestDir
  1187.             End If
  1188.  
  1189.             'Special case for CTL3D32.DLL
  1190.             '-- we never install these files unders Windows 95 or NT4, only under Windows NT3.51
  1191.             If strDestName = mstrFILE_CTL3D32 Then
  1192.                 If TreatAsWin95() Then
  1193.                     'We're not running under NT 3.51 - do not install this file.
  1194.                     intRC = IDIGNORE
  1195.                     LogNote ResolveResString(resCOMMON_CTL3D32NOTCOPIED, "|1", strDestName)
  1196.                     AbortAction
  1197.                 End If
  1198.             End If
  1199.             
  1200.             'Special case for RICHED32.DLL
  1201.             '-- we only install this file under Windows 95, not under Windows NT (3.51 or 4.0)
  1202.             If strDestName = mstrFILE_RICHED32 Then
  1203.                 If Not IsWindows95() Then
  1204.                     'We're not running under Win95 - do not install this file.
  1205.                     intRC = IDIGNORE
  1206.                     LogNote ResolveResString(resCOMMON_RICHED32NOTCOPIED, "|1", strDestName)
  1207.                     AbortAction
  1208.                 End If
  1209.             End If
  1210.             '
  1211.             ' Special case for AXDIST.EXE
  1212.             ' If this is Win95 or NT4 and AXDIST.EXE is in the setup list, we need
  1213.             ' to execute it when setup1 is complete.  AXDIST.EXE is a self-extracting
  1214.             ' exe that installs special files needed for internet functionality.
  1215.             '
  1216.             If UCase(strDestName) = gstrFILE_AXDIST Then
  1217.                 '
  1218.                 ' Don't do anything here if this is not Win95 or NT4.
  1219.                 '
  1220.                 If Not TreatAsWin95() Then
  1221.                     'We're not running under Win95 or NT4- do not install this file.
  1222.                     intRC = IDIGNORE
  1223.                     LogNote ResolveResString(resCOMMON_AXDISTNOTCOPIED, "|1", strDestName)
  1224.                     AbortAction
  1225.                     gfAXDist = False
  1226.                 End If
  1227.             End If
  1228.             '
  1229.             ' Special case for WINt351.EXE
  1230.             ' If this is NT3.51 and WINt351.EXE is in the setup list, we need
  1231.             ' to execute it when setup1 is complete.  WINt351.EXE is a self-extracting
  1232.             ' exe that installs special files needed for internet functionality.
  1233.             '
  1234.             If UCase(strDestName) = gstrFILE_WINT351 Then
  1235.                 '
  1236.                 ' Don't do anything here if this is not NT3.51.
  1237.                 '
  1238.                 If TreatAsWin95() Then
  1239.                     'We're not running under NT3.51- do not install this file.
  1240.                     intRC = IDIGNORE
  1241.                     LogNote ResolveResString(resCOMMON_WINT351NOTCOPIED, "|1", strDestName)
  1242.                     AbortAction
  1243.                     gfWINt351 = False
  1244.                 End If
  1245.             End If
  1246.             
  1247.             strRegister = sFile.strRegister
  1248.  
  1249.             lThisFileSize = CalcFinalSize(sFile.lFileSize, sFile.strDestDir)
  1250.  
  1251.             '
  1252.             'The stuff below trys to save some time by pre-checking whether a file
  1253.             'should be installed before a split file is concatenated or before
  1254.             'VerInstallFile does its think which involves a full file read (for
  1255.             'a compress file) at the minimum.  Basically, if both files have
  1256.             'version numbers, they are compared.  If one file has a version number
  1257.             'and the other doesn't, the one with the version number is deemed
  1258.             '"Newer".  If neither file has a version number, we compare date.
  1259.             '
  1260.             'Always attempt to get the source file version number.  If the setup
  1261.             'info file did not contain a version number (sSrcVerInfo.nMSHi =
  1262.             'gintNOVERINFO), we attempt to read the version number from the source
  1263.             'file.  Reading the version number from a split file will always fail.
  1264.             'That's why it's a good idea to include the version number for a file
  1265.             '(especially split ones) in the setup info file (SETUP.LST)
  1266.             '
  1267.             fSrcVer = True
  1268.             sSrcVerInfo = sFile.sVerInfo
  1269.             If sSrcVerInfo.nMSHi = gintNOVERINFO Then
  1270.                 fSrcVer = GetFileVerStruct(strSrcDir & strSrcName, sSrcVerInfo)
  1271.             End If
  1272.  
  1273.             '
  1274.             'If there is an existing destination file with version information, then
  1275.             'compare its version number to the source file version number.
  1276.             '
  1277.             If intRC <> IDIGNORE Then
  1278.                 fRemoteReg = (sFile.strRegister = mstrREMOTEREGISTER)
  1279.                 If GetFileVerStruct(strDestDir & strDestName, sDestVerInfo, fRemoteReg) = True Then
  1280.                     If fSrcVer = True Then
  1281.                         If IsNewerVer(sSrcVerInfo, sDestVerInfo) = False Then
  1282.                             '
  1283.                             'Existing file is newer than the one we want to install;
  1284.                             'the existing file will be used instead
  1285.                             '
  1286.                             intRC = IDIGNORE
  1287.                             fFileWasUpToDate = True
  1288.                             DecideIncrementRefCount strDestDir & strDestName, sFile.fShared, sFile.fSystem, True
  1289.                             AddActionNote ResolveResString(resLOG_FILEUPTODATE)
  1290.                             CommitAction
  1291.                         End If
  1292.                     End If
  1293.                 Else
  1294.                     '
  1295.                     'If the destination file has no version info, then we'll copy the
  1296.                     'source file if it *does* have a version.  If neither file has a
  1297.                     'version number, then we compare date.
  1298.                     '
  1299.                     If fSrcVer = False Then
  1300.                         If sFile.varDate <= CVDate(FileDateTime(strDestDir & strDestName)) Then
  1301.                             If Err = 0 Then
  1302.                                 '
  1303.                                 'Although neither the source nor the existing file contain version
  1304.                                 'information, the existing file has a newer date so we'll use it.
  1305.                                 '
  1306.                                 intRC = IDIGNORE
  1307.                                 fFileWasUpToDate = True
  1308.                                 DecideIncrementRefCount strDestDir & strDestName, sFile.fShared, sFile.fSystem, True
  1309.                                 AddActionNote ResolveResString(resLOG_FILEUPTODATE)
  1310.                                 CommitAction
  1311.                             Else
  1312.                                 Err = 0
  1313.                             End If
  1314.                         End If
  1315.                     End If
  1316.                 End If
  1317.             End If
  1318.             
  1319.             '
  1320.             'If we've decided to try the copy, and if this is the first extent of a split file
  1321.             'then open the temporary file used for concatentation
  1322.             '
  1323.             If intRC <> IDIGNORE And fSplit = True Then
  1324.                 mintConcatFile = OpenConcatFile()
  1325.                 If mintConcatFile = -1 Then
  1326.                     'The open failed, and the user chose to ignore the error
  1327.                     mintConcatFile = 0
  1328.                     intRC = IDIGNORE
  1329.                     AbortAction
  1330.                 End If
  1331.             End If
  1332.         End If
  1333.  
  1334.         '
  1335.         'If this is an extent of a split file, and we're going to try the copy, then
  1336.         'append this source file extent to the end of the concatentation file
  1337.         '
  1338.         If fSplit = True Then
  1339.             If intRC <> IDIGNORE Then
  1340.                 intRC = ConcatSplitFile(strSrcDir & strSrcName)
  1341.                 If intRC = IDIGNORE Then
  1342.                     AbortAction
  1343.                 End If
  1344.             End If
  1345.  
  1346.             If intRC = IDIGNORE And mintConcatFile > 0 Then
  1347.                 Close mintConcatFile
  1348.                 mintConcatFile = 0
  1349.             End If
  1350.  
  1351.             fSplit = sFile.fSplit
  1352.         End If
  1353.  
  1354.         '
  1355.         'If the file wasn't split, or if this is the last extent of a split file
  1356.         '
  1357.         If fSplit = False Then
  1358.             If mintConcatFile > 0 Then
  1359.                 '
  1360.                 'If this was the last extent of a split file, close the concatenated
  1361.                 'file.  At this point, the concatentated file is a true representation
  1362.                 'of the desired source file, so we point to it instead of the split file
  1363.                 'extent on the installation media
  1364.                 '
  1365.                 Close mintConcatFile
  1366.                 strSrcDir = mstrConcatDrive
  1367.                 strSrcName = mstrCONCATFILE
  1368.             End If
  1369.  
  1370.             '
  1371.             'After all of this, if we're still ready to copy, then give it a whirl!
  1372.             '
  1373.             If intRC <> IDIGNORE Then
  1374.                 ' CopyFile will increment the reference count for us, and will either
  1375.                 ' commit or abort the current Action.
  1376.                 intRC = IIf(CopyFile(strSrcDir, strDestDir, strSrcName, strDestName, sFile.fShared, sFile.fSystem), 0, IDIGNORE)
  1377.             End If
  1378.  
  1379.             '
  1380.             'Save the paths of certain files for later use, if they were
  1381.             'successfully installed or were already on the system
  1382.             '
  1383.             If (intRC = 0 Or fFileWasUpToDate) Then
  1384.                 Select Case strDestName
  1385.                     Case mstrFILE_AUTMGR32
  1386.                         '
  1387.                         'Used for creating an icon if installed
  1388.                         '
  1389.                         gsDest.strAUTMGR32 = strDestDir & mstrFILE_AUTMGR32
  1390.                     Case mstrFILE_RACMGR32
  1391.                         '
  1392.                         'Used for creating an icon if installed
  1393.                         '
  1394.                         gsDest.strRACMGR32 = strDestDir & mstrFILE_RACMGR32
  1395.                     'End Case
  1396.                 End Select
  1397.             
  1398.                 '
  1399.                 'If we successfully copied the file, and if registration information was
  1400.                 'specified in the setup info file, save the registration info into an
  1401.                 'array so that we can register all files requiring it in one fell swoop
  1402.                 'after all the files have been copied.
  1403.                 '
  1404.                 If strRegister <> gstrNULL Then
  1405.                     Err = 0
  1406.                     ReDim Preserve msRegInfo(UBound(msRegInfo) + 1)
  1407.     
  1408.                     If Err > 0 Then
  1409.                         ReDim msRegInfo(0)
  1410.                     End If
  1411.     
  1412.                     msRegInfo(UBound(msRegInfo)).strFilename = strDestDir & strDestName
  1413.     
  1414.                     Select Case strRegister
  1415.                         Case mstrDLLSELFREGISTER, mstrEXESELFREGISTER, mstrTLBREGISTER, mstrVBLREGISTER
  1416.                             'Nothing in particular to do
  1417.                         Case mstrREMOTEREGISTER
  1418.                             'We need to look for and parse the corresponding "RemoteX=..." line
  1419.                             If Not ReadSetupRemoteLine(strSection, intIdx, msRegInfo(UBound(msRegInfo))) = True Then
  1420.                                 MsgError ResolveResString(resREMOTELINENOTFOUND, "|1", strDestName, "|2", gstrINI_REMOTE & Format$(intIdx)), vbExclamation Or vbOKOnly, gstrTitle
  1421.                                 ExitSetup frmSetup1, gintRET_FATAL
  1422.                             End If
  1423.                         Case Else
  1424.                             '
  1425.                             'If the registration info specified the name of a file with
  1426.                             'registration info (which we assume if a registration macro
  1427.                             'was not specified), then we also assume that, if no path
  1428.                             'information is available, this reginfo file is in the same
  1429.                             'directory as the file it registers
  1430.                             '
  1431.                             strRegister = ResolveDestDirs(strRegister)
  1432.                             If InStr(strRegister, gstrSEP_DIR) = 0 Then
  1433.                                 strRegister = strDestDir & strRegister
  1434.                             End If
  1435.                         'End Case
  1436.                     End Select
  1437.     
  1438.                     msRegInfo(UBound(msRegInfo)).strRegister = strRegister
  1439.                 End If
  1440.             
  1441.             End If
  1442.  
  1443.             '
  1444.             'If we created a temporary concatenation file, nuke it
  1445.             '
  1446.             If mintConcatFile > 0 Then
  1447.                 Kill mstrConcatDrive & mstrCONCATFILE
  1448.                 mintConcatFile = 0
  1449.             End If
  1450.         End If
  1451.  
  1452.         strLastFile = sFile.strDestName
  1453.  
  1454. CSContinue:
  1455.         '
  1456.         'If the file wasn't split, or if this was the last extent of a split file, then
  1457.         'update the copy status bar.  We need to do the update regardless of whether a
  1458.         'file was actually copied or not.
  1459.         '
  1460.         If sFile.fSplit = False Then
  1461.             glTotalCopied = glTotalCopied + lThisFileSize
  1462.             UpdateStatus frmCopy.picStatus, glTotalCopied / mlTotalToCopy
  1463.         End If
  1464.  
  1465.         '
  1466.         'Give a chance for the 'Cancel' button command to be processed if it was pressed
  1467.         '
  1468.         DoEvents
  1469.         intIdx = intIdx + 1
  1470.     Loop
  1471.  
  1472.     Err = 0
  1473. End Sub
  1474.  
  1475. '-----------------------------------------------------------
  1476. ' SUB: CreateOSProgramGroup
  1477. '
  1478. ' Calls CreateProgManGroup under Windows NT or
  1479. ' fCreateShellGroup under Windows 95
  1480. '-----------------------------------------------------------
  1481. '
  1482. Function fCreateOSProgramGroup(frm As Form, ByVal strFolderName As String, ByVal fRetOnErr As Boolean, Optional ByVal fLog As Boolean = True) As Boolean
  1483.     If TreatAsWin95() Then
  1484.         fCreateOSProgramGroup = fCreateShellGroup(strFolderName, fRetOnErr, fLog)
  1485.     Else
  1486.         CreateProgManGroup frm, strFolderName, fRetOnErr, fLog
  1487.         fCreateOSProgramGroup = True
  1488.     End If
  1489. End Function
  1490.  
  1491. '-----------------------------------------------------------
  1492. ' SUB: CreateOSLink
  1493. '
  1494. ' Calls CreateProgManItem under Windows NT or
  1495. ' CreateFolderLink under Windows 95.
  1496. '
  1497. ' If fLog is missing, the default is True.
  1498. '-----------------------------------------------------------
  1499. '
  1500. Sub CreateOSLink(frm As Form, ByVal strGroupName As String, ByVal strLinkPath As String, ByVal strLinkArguments As String, ByVal strLinkName As String, Optional ByVal fLog)
  1501.     If IsMissing(fLog) Then
  1502.         fLog = True
  1503.     End If
  1504.     
  1505.     If TreatAsWin95() Then
  1506.         CreateShellLink strLinkPath, strGroupName, strLinkArguments, strLinkName, fLog
  1507.     Else
  1508.         '
  1509.         ' DDE will not work properly if you try to send NT the long filename.  If it is
  1510.         ' in quotes, then the parameters get ignored.  If there are no parameters, the
  1511.         ' long filename can be used and the following line could be skipped.
  1512.         '
  1513.         strLinkPath = GetShortPathName(strUnQuoteString(strLinkPath))
  1514.         CreateProgManItem frm, strGroupName, strLinkPath & " " & strLinkArguments, strLinkName, fLog
  1515.     End If
  1516. End Sub
  1517.  
  1518. '-----------------------------------------------------------
  1519. ' SUB: CreateProgManGroup
  1520. '
  1521. ' Creates a new group in the Windows program manager if
  1522. ' the specified groupname doesn't already exist
  1523. '
  1524. ' IN: [frm] - form containing a label named 'lblDDE'
  1525. '     [strGroupName] - text name of the group
  1526. '     [fRetOnErr]    - ignored
  1527. '     [fLog] - Whether or not to write to the logfile (default
  1528. '                is true if missing)
  1529. '-----------------------------------------------------------
  1530. '
  1531. Sub CreateProgManGroup(frm As Form, ByVal strGroupName As String, ByVal fRetOnErr As Boolean, Optional ByVal fLog)
  1532.     '
  1533.     'Call generic progman DDE function with flag to add a group
  1534.     '
  1535.     If IsMissing(fLog) Then
  1536.         fLog = True
  1537.     End If
  1538.     
  1539.     'Perform the DDE to create the group
  1540.     PerformDDE frm, strGroupName, gstrNULL, gstrNULL, mintDDE_GRPADD, fLog
  1541. End Sub
  1542.  
  1543. '-----------------------------------------------------------
  1544. ' SUB: CreateProgManItem
  1545. '
  1546. ' Creates (or replaces) a program manager icon in the active
  1547. ' program manager group
  1548. '
  1549. ' IN: [frm] - form containing a label named 'lblDDE'
  1550. '     [strGroupName] - Caption of group in which icon will go.
  1551. '     [strCmdLine] - command line for the item/icon,
  1552. '                    Ex: 'c:\myapp\myapp.exe'
  1553. '                    Note:  If this path contains spaces
  1554. '                      or commas, it should be enclosed
  1555. '                      with quotes so that it is properly
  1556. '                      interpreted by Windows (see AddQuotesToFN)
  1557. '     [strIconTitle] - text caption for the icon
  1558. '     [fLog] - Whether or not to write to the logfile (default
  1559. '                is true if missing)
  1560. '
  1561. ' PRECONDITION: CreateProgManGroup has already been called.  The
  1562. '               new icon will be created in the group last created.
  1563. '-----------------------------------------------------------
  1564. '
  1565. Sub CreateProgManItem(frm As Form, ByVal strGroupName As String, ByVal strCmdLine As String, ByVal strIconTitle As String, Optional ByVal fLog)
  1566.     '
  1567.     'Call generic progman DDE function with flag to add an item
  1568.     '
  1569.     If IsMissing(fLog) Then
  1570.         fLog = True
  1571.     End If
  1572.     PerformDDE frm, strGroupName, strCmdLine, strIconTitle, mintDDE_ITEMADD, fLog
  1573. End Sub
  1574.  
  1575. '-----------------------------------------------------------
  1576. ' SUB: fCreateShellGroup
  1577. '
  1578. ' Creates a new program group off of Start>Programs in the
  1579. ' Windows 95 shell if the specified folder doesn't already exist.
  1580. '
  1581. ' IN: [strFolderName] - text name of the folder.
  1582. '                      This parameter may not contain
  1583. '                      backslashes.
  1584. '                      ex: "My Application" - this creates
  1585. '                        the folder Start>Programs>My Application
  1586. '     [fRetOnerr] - Whether or not this routine should return if
  1587. '                   there is an error creating the group.  If false,
  1588. '                   setup aborts and does not return.  Set this to
  1589. '                   true if the user can do something to correct the
  1590. '                   error.  E.g., they entered a group name in the
  1591. '                   Choose Program Group dialog as opposed to calling
  1592. '                   this routine when creating the Remote Automation
  1593. '                   group in which the user had no control.
  1594. '     [fLog] - Whether or not to write to the logfile (default
  1595. '                is true if missing)
  1596. '-----------------------------------------------------------
  1597. '
  1598. Function fCreateShellGroup(ByVal strFolderName As String, fRetOnErr As Boolean, Optional ByVal fLog) As Boolean
  1599.     If IsMissing(fLog) Then
  1600.         fLog = True
  1601.     End If
  1602.  
  1603.     ReplaceDoubleQuotes strFolderName
  1604.     
  1605.     If strFolderName = "" Then
  1606.         Exit Function
  1607.     End If
  1608.  
  1609.     If fLog Then
  1610.         NewAction gstrKEY_SHELLFOLDER, """" & strFolderName & """"
  1611.     End If
  1612.  
  1613. Retry:
  1614.     
  1615.     Dim fSuccess As Boolean
  1616.     fSuccess = OSfCreateShellGroup(strFolderName)
  1617.     If fSuccess Then
  1618.         If fLog Then
  1619.             CommitAction
  1620.         End If
  1621.     Else
  1622.         If gfNoUserInput Or (MsgError(ResolveResString(resCANTCREATEPROGRAMGROUP, "|1", strFolderName), vbRetryCancel Or vbExclamation, gstrTitle)) = IDCANCEL Then
  1623.             ExitSetup frmSetup1, gintRET_EXIT
  1624.             GoTo Retry
  1625.         End If
  1626.         '
  1627.         ' Determine if we should return so the user can
  1628.         ' correct the situation.
  1629.         '
  1630.         If Not fRetOnErr Then
  1631.             '
  1632.             ' Return so we can exit setup.
  1633.             '
  1634.             GoTo Retry
  1635.         End If
  1636.     End If
  1637.  
  1638.     
  1639.     fCreateShellGroup = fSuccess
  1640. End Function
  1641.  
  1642. '-----------------------------------------------------------
  1643. ' SUB: CreateShellLink
  1644. '
  1645. ' Creates (or replaces) a link in either Start>Programs or
  1646. ' any of its immediate subfolders in the Windows 95 shell.
  1647. '
  1648. ' IN: [strLinkPath] - full path to the target of the link
  1649. '                     Ex: 'c:\Program Files\My Application\MyApp.exe"
  1650. '     [strLinkArguments] - command-line arguments for the link
  1651. '                     Ex: '-f -c "c:\Program Files\My Application\MyApp.dat" -q'
  1652. '     [strLinkName] - text caption for the link
  1653. '     [fLog] - Whether or not to write to the logfile (default
  1654. '                is true if missing)
  1655. '
  1656. ' OUT:
  1657. '   The link will be created in the folder strGroupName
  1658.  
  1659. '-----------------------------------------------------------
  1660. '
  1661. Sub CreateShellLink(ByVal strLinkPath As String, ByVal strGroupName As String, ByVal strLinkArguments As String, ByVal strLinkName As String, Optional ByVal fLog)
  1662.     If IsMissing(fLog) Then
  1663.         fLog = True
  1664.     End If
  1665.     
  1666.     If fLog Then
  1667.         NewAction gstrKEY_SHELLLINK, """" & strUnQuoteString(strGroupName) & """" & ", " & """" & strUnQuoteString(strLinkName) & """"
  1668.     End If
  1669.     
  1670.     'ReplaceDoubleQuotes strLinkName
  1671.     strLinkName = strUnQuoteString(strLinkName)
  1672.     strLinkPath = strUnQuoteString(strLinkPath)
  1673.     
  1674.  
  1675. Retry:
  1676.  
  1677.     Dim fSuccess As Boolean
  1678.     fSuccess = OSfCreateShellLink(strGroupName & "", strLinkName, strLinkPath, strLinkArguments & "") 'the path should never be enclosed in double quotes
  1679.     If fSuccess Then
  1680.         If fLog Then
  1681.             CommitAction
  1682.         End If
  1683.     Else
  1684.         Dim intMsgRet As Integer
  1685.         intMsgRet = MsgError(ResolveResString(resCANTCREATEPROGRAMICON, "|1", strLinkName), vbAbortRetryIgnore Or vbExclamation, gstrTitle)
  1686.         If gfNoUserInput Then
  1687.             intMsgRet = vbAbort
  1688.         End If
  1689.         Select Case intMsgRet
  1690.             Case vbAbort
  1691.                 ExitSetup frmSetup1, gintRET_ABORT
  1692.                 GoTo Retry
  1693.             Case vbRetry
  1694.                 GoTo Retry
  1695.             Case vbIgnore
  1696.                 If fLog Then
  1697.                     AbortAction
  1698.                 End If
  1699.             'End Case
  1700.         End Select
  1701.     End If
  1702. End Sub
  1703.  
  1704. '-----------------------------------------------------------
  1705. ' FUNCTION: DecideIncrementRefCount
  1706. '
  1707. ' Increments the reference count of a file under 32-bits
  1708. ' if the file is a shared file.
  1709. '
  1710. ' IN: [strFullPath] - full pathname of the file to reference
  1711. '                     count.  Example:
  1712. '                     'C:\MYAPP\MYAPP.DAT'
  1713. '     [fShared] - whether the file is shared or private
  1714. '     [fSystem] - The file is a system file
  1715. '     [fFileAlreadyExisted] - whether or not the file already
  1716. '                             existed on the hard drive
  1717. '                             before our setup program
  1718. '-----------------------------------------------------------
  1719. '
  1720. Sub DecideIncrementRefCount(ByVal strFullPath As String, ByVal fShared As Boolean, ByVal fSystem As Boolean, ByVal fFileAlreadyExisted As Boolean)
  1721.     'Reference counting takes place under both Windows 95 and Windows NT
  1722.     If fShared Or fSystem Then
  1723.         IncrementRefCount strFullPath, fFileAlreadyExisted
  1724.     End If
  1725. End Sub
  1726.             
  1727. '-----------------------------------------------------------
  1728. ' FUNCTION: DetectFile
  1729. '
  1730. ' Detects whether the specified file exists.  If it can't
  1731. ' be found, the user is given the opportunity to abort,
  1732. ' retry, or ignore finding the file.  This call is used,
  1733. ' for example, to ensure that a floppy with the specified
  1734. ' file name is in the drive before continuing.
  1735. '
  1736. ' IN: [strFileName] - name of file to detect, usually
  1737. '                     should include full path, Example:
  1738. '                     'A:\MYAPP.DAT'
  1739. '
  1740. ' Returns: TRUE if the file was detected, IDIGNORE if
  1741. '          the user chose ignore when the file couldn't
  1742. '          be found, or calls ExitSetup upon 'Abort'
  1743. '-----------------------------------------------------------
  1744. '
  1745. Function DetectFile(ByVal strFilename As String) As Integer
  1746.     Dim strMsg As String
  1747.  
  1748.     DetectFile = True
  1749.                       
  1750.     Do While FileExists(strFilename) = False
  1751.  
  1752.  
  1753.         strMsg = ResolveResString(resCANTOPEN) & LS$ & strFilename
  1754.         Select Case MsgError(strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or IIf(gfNoUserInput, vbDefaultButton1, MB_DEFBUTTON2), gstrSETMSG)
  1755.             Case IDABORT
  1756.                 ExitSetup frmCopy, gintRET_ABORT
  1757.             Case IDIGNORE
  1758.                 DetectFile = IDIGNORE
  1759.                 Exit Do
  1760.             'End Case
  1761.         End Select
  1762.     Loop
  1763. End Function
  1764.  
  1765.  
  1766. '-----------------------------------------------------------
  1767. ' SUB: EtchedLine
  1768. '
  1769. ' Draws an 'etched' line upon the specified form starting
  1770. ' at the X,Y location passed in and of the specified length.
  1771. ' Coordinates are in the current ScaleMode of the passed
  1772. ' in form.
  1773. '
  1774. ' IN: [frmEtch] - form to draw the line upon
  1775. '     [intX1] - starting horizontal of line
  1776. '     [intY1] - starting vertical of line
  1777. '     [intLength] - length of the line
  1778. '-----------------------------------------------------------
  1779. '
  1780. Sub EtchedLine(frmEtch As Form, ByVal intX1 As Integer, ByVal intY1 As Integer, ByVal intLength As Integer)
  1781.     Const lWHITE& = vb3DHighlight
  1782.     Const lGRAY& = vb3DShadow
  1783.  
  1784.     frmEtch.Line (intX1, intY1)-(intX1 + intLength, intY1), lGRAY
  1785.     frmEtch.Line (frmEtch.CurrentX + 5, intY1 + 20)-(intX1 - 5, intY1 + 20), lWHITE
  1786. End Sub
  1787.  
  1788. '-----------------------------------------------------------
  1789. ' SUB: ExeSelfRegister
  1790. '
  1791. ' Synchronously runs the file passed in (which should be
  1792. ' an executable file that supports the /REGSERVER switch,
  1793. ' for instance, a VB5 generated ActiveX Component .EXE).
  1794. '
  1795. ' IN: [strFileName] - .EXE file to register
  1796. '-----------------------------------------------------------
  1797. '
  1798. Sub ExeSelfRegister(ByVal strFilename As String)
  1799.     Const strREGSWITCH$ = " /REGSERVER"
  1800.  
  1801.     Dim fShell As Integer
  1802.  
  1803.     '
  1804.     'Synchronously shell out and run the .EXE with the self registration switch
  1805.     '
  1806.     fShell = FSyncShell(AddQuotesToFN(strFilename) & strREGSWITCH, 7)
  1807.     frmSetup1.Refresh
  1808. End Sub
  1809.  
  1810. '-----------------------------------------------------------
  1811. ' SUB: ExitSetup
  1812. '
  1813. ' Handles shutdown of the setup app.  Depending upon the
  1814. ' value of the intExitCode parm, may prompt the user and
  1815. ' exit the sub if the user chooses to cancel the exit
  1816. ' process.
  1817. '
  1818. ' IN: [frm] - active form to unload upon exit
  1819. '     [intExitCode] - code specifying exit action
  1820. '-----------------------------------------------------------
  1821. '
  1822. Sub ExitSetup(frm As Form, intExitCode As Integer)
  1823.     Dim strMsg As String
  1824.     Dim strSilent As String
  1825.  
  1826.     On Error Resume Next
  1827.     '
  1828.     ' If we aren't running in silent or sms mode give
  1829.     ' the user a chance to try again, if applicable.
  1830.     '
  1831.     If Not gfNoUserInput Then
  1832.         Select Case intExitCode
  1833.             Case gintRET_EXIT
  1834.                 '
  1835.                 'If user chose an Exit or Cancel button
  1836.                 '
  1837.                 If MsgWarning(ResolveResString(resASKEXIT), MB_ICONQUESTION Or MB_YESNO Or MB_DEFBUTTON2, gstrTitle) = IDNO Then
  1838.                     Exit Sub
  1839.                 End If
  1840.             Case gintRET_ABORT
  1841.                 '
  1842.                 'If user chose to abort before a pending action
  1843.                 '
  1844.                 strMsg = ResolveResString(resINCOMPLETE) & LS$ & ResolveResString(resQUITNOW) & LS$
  1845.                 strMsg = strMsg & ResolveResString(resQUITSETUP)
  1846.                 If MsgWarning(strMsg, MB_ICONQUESTION Or MB_YESNO Or IIf(gfNoUserInput, vbDefaultButton1, MB_DEFBUTTON2), gstrSETMSG) = IDNO Then
  1847.                     Exit Sub
  1848.                 End If
  1849.             'End Case
  1850.         End Select
  1851.     End If
  1852.  
  1853.     'Abort any pending actions
  1854.     While fWithinAction()
  1855.         AbortAction
  1856.     Wend
  1857.     
  1858.     Close
  1859.  
  1860.     '
  1861.     'Clean up any temporary files from VerInstallFile or split file concatenation
  1862.     '
  1863.     Kill mstrVerTmpName
  1864.     If mintConcatFile > 0 Then
  1865.         Close mintConcatFile
  1866.         Kill mstrConcatDrive & mstrCONCATFILE
  1867.     End If
  1868.  
  1869.     If frm.hwnd <> frmSetup1.hwnd Then
  1870.         Unload frm
  1871.     End If
  1872.     
  1873.     frmSetup1.SetFocus
  1874.  
  1875.     '
  1876.     'Give appropriate ending message depending upon exit code
  1877.     '
  1878.     Select Case intExitCode
  1879.         Case gintRET_EXIT, gintRET_ABORT
  1880.             gfSMSStatus = False
  1881.             strMsg = ResolveResString(resINTERRUPTED, "|1", gstrAppName) & LS$ & ResolveResString(resCANRUN, "|1", gstrAppName)
  1882.             MsgWarning strMsg, MB_OK Or MB_ICONSTOP, gstrTitle
  1883.         Case gintRET_FATAL
  1884.             gfSMSStatus = False
  1885.             MsgError ResolveResString(resERROR, "|1", gstrAppName), MB_OK Or MB_ICONSTOP, gstrTitle
  1886.         Case gintRET_FINISHEDSUCCESS
  1887.             gfSMSStatus = True
  1888.             '
  1889.             ' Don't log this message to SMS since it is only a confirmation.
  1890.             '
  1891.             gfDontLogSMS = True
  1892.             MsgFunc ResolveResString(resSUCCESS, "|1", gstrAppName), MB_OK, gstrTitle
  1893.             
  1894.             If IsWindowsNT4WithoutSP2() Then
  1895.                 'Recommend that the user upgrade to NT 4.0 SP2
  1896.                 gfDontLogSMS = True
  1897.                 MsgWarning ResolveResString(resNT4WithoutSP2), MB_OK Or MB_ICONINFORMATION, gstrTitle
  1898.             End If
  1899.         Case Else
  1900.             strMsg = ResolveResString(resINTERRUPTED, "|1", gstrAppName) & LS$ & ResolveResString(resCANRUN, "|1", gstrAppName)
  1901.             MsgWarning strMsg, MB_OK Or MB_ICONSTOP, gstrTitle
  1902.         'End Case
  1903.     End Select
  1904.  
  1905.     'Stop logging
  1906.     DisableLogging
  1907.     
  1908.     ' Clean up an aborted installation
  1909.     If (intExitCode = gintRET_FINISHEDSUCCESS) Then
  1910.         'Setup finished successfully - Temporary files should
  1911.         'have already been cleaned up.  Nothing else to do.
  1912.     Else
  1913.         'Setup has been aborted for one reason or another
  1914.         If (gstrAppRemovalEXE <> "") Then
  1915.             Dim nErrorLevel As Integer
  1916.             Select Case intExitCode
  1917.                 Case gintRET_FATAL
  1918.                     nErrorLevel = APPREMERR_FATAL
  1919.                 Case gintRET_EXIT
  1920.                     nErrorLevel = APPREMERR_USERCANCEL
  1921.                 Case gintRET_ABORT
  1922.                     nErrorLevel = APPREMERR_NONFATAL
  1923.                 Case Else
  1924.                     nErrorLevel = APPREMERR_FATAL
  1925.                 'End Case
  1926.             End Select
  1927.         
  1928.             '
  1929.             ' We don't want to log this message to sms because it is
  1930.             ' only a confirmation message.
  1931.             '
  1932.             gfDontLogSMS = True
  1933.             MsgFunc ResolveResString(resLOG_ABOUTTOREMOVEAPP), vbInformation Or vbOKOnly, gstrTitle
  1934.             
  1935.             Err = 0
  1936.             '
  1937.             ' Ready to run the installer.  Determine if this is a
  1938.             ' silent uninstall or not.
  1939.             '
  1940.             If gfSilent Then
  1941.                 strSilent = gstrSilentLog
  1942.             Else
  1943.                 strSilent = gstrNULL
  1944.             End If
  1945.             
  1946.             Shell GetAppRemovalCmdLine(gstrAppRemovalEXE, gstrAppRemovalLog, strSilent, gfSMS, nErrorLevel, True), vbNormalFocus
  1947.             If Err Then
  1948.                 MsgError Error$ & LS$ & ResolveResString(resLOG_CANTRUNAPPREMOVER), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  1949.             End If
  1950.  
  1951.             'Since the app removal program will attempt to delete this program and all of our runtime
  1952.             'files, we should exit as soon as possible (otherwise the app remover will not be
  1953.             'able to remove these files)
  1954.         End If
  1955.         
  1956.         'Note: We do not delete the logfile if an error occurs.
  1957.         'The application removal EXE will do that if needed.
  1958.         
  1959.     End If
  1960.     
  1961.     Unload frmSetup1
  1962.  
  1963.     If gfSMS = True Then
  1964.         WriteMIF gstrMIFFile, gfSMSStatus, gstrSMSDescription
  1965.     End If
  1966.  
  1967.     'End the program
  1968.     End
  1969. End Sub
  1970.  
  1971. '-----------------------------------------------------------
  1972. ' FUNCTION: ProcessCommandLine
  1973. '
  1974. ' Processes the command-line arguments
  1975. '
  1976. ' OUT: Fills in the passed-in byref parameters as appropriate
  1977. '-----------------------------------------------------------
  1978. '
  1979. Sub ProcessCommandLine(ByVal strCommand As String, ByRef fSilent As Boolean, ByRef strSilentLog As String, ByRef fSMS As Boolean, ByRef strMIFFile As String, ByRef strSrcPath As String, ByRef strAppRemovalLog As String, ByRef strAppRemovalEXE As String)
  1980.     Dim fErr As Boolean
  1981.     Dim intAnchor As Integer
  1982.     
  1983.     Const strTemp$ = ""
  1984.     
  1985.     strSrcPath = ""
  1986.     strAppRemovalLog = ""
  1987.     
  1988.     strCommand = Trim$(strCommand)
  1989.     
  1990.     '
  1991.     ' First, check to see if this is supposed to be a silent
  1992.     ' install (/s or -s on the command line followed by
  1993.     ' a log file name) and set global variables appropriately.
  1994.     '
  1995.     ' If you are designing a silent install, the /s or -s
  1996.     ' command line parameter should be added to the setup.exe
  1997.     ' command.  It will automatically be passed to setup1 as the
  1998.     ' first parameter.
  1999.     '
  2000.     ' The filename that follows the /s or -s parameter must
  2001.     ' include the full path name.
  2002.     '
  2003.     intAnchor = InStr(LCase(strCommand), gstrSwitchPrefix1 & gstrSILENTSWITCH)
  2004.     If intAnchor = 0 Then
  2005.         intAnchor = InStr(LCase(strCommand), gstrSwitchPrefix2 & gstrSILENTSWITCH)
  2006.     End If
  2007.     If intAnchor > 0 Then
  2008.         fSilent = True
  2009.         strCommand = Trim(Mid(strCommand, intAnchor + 2))
  2010.         strSilentLog = strExtractFilenameArg(strCommand, fErr)
  2011.         If fErr Then GoTo BadCommandLine
  2012.     Else
  2013.         fSilent = False
  2014.     End If
  2015.     '
  2016.     ' Next, check to see if this is supposed to be an SMS
  2017.     ' silent install.  If setup was started with the /q or -q
  2018.     ' switch, then this is an SMS silent install.  /q or -q
  2019.     ' must be followed by the name of the SMS MIF file to
  2020.     ' write status information to.  When calling setup.exe
  2021.     ' pass the name of the application exe as your MIF file
  2022.     ' name (e.g., /q MyProg.Exe).  Setup.exe will take this
  2023.     ' filename and convert it to c:\windows\MyProg.MIF (assuming
  2024.     ' windows in the "c:\windows" directory) and pass it to
  2025.     ' setup1.  Essentially, setup.exe will take whatever file
  2026.     ' name is passed to it after the q switch, remove the path
  2027.     ' and extension and then add the windows directory path and
  2028.     ' MIF extension to it to create the name of the MIF file.
  2029.     ' It doesn't matter if you pass the full path or not to
  2030.     ' setup.exe.
  2031.     '
  2032.     intAnchor = InStr(LCase(strCommand), gstrSwitchPrefix1 & gstrSMSSWITCH)
  2033.     If intAnchor = 0 Then
  2034.         intAnchor = InStr(LCase(strCommand), gstrSwitchPrefix2 & gstrSMSSWITCH)
  2035.     End If
  2036.     If intAnchor > 0 Then
  2037.         fSMS = True
  2038.         strCommand = Trim(Mid(strCommand, intAnchor + 2))
  2039.         strMIFFile = strExtractFilenameArg(strCommand, fErr)
  2040.         If fErr Then GoTo BadCommandLine
  2041.     Else
  2042.         fSMS = False
  2043.     End If
  2044.     
  2045.     '
  2046.     ' We expect to find the source directory,
  2047.     ' name/path of the logfile, and name/path
  2048.     ' of the app removal executable, separated only by
  2049.     ' spaces
  2050.     '
  2051.     strSrcPath = strExtractFilenameArg(strCommand, fErr)
  2052.     If fErr Then GoTo BadCommandLine
  2053.     
  2054.     strAppRemovalLog = strExtractFilenameArg(strCommand, fErr)
  2055.     If fErr Then GoTo BadCommandLine
  2056.     
  2057.  
  2058.     strAppRemovalEXE = strExtractFilenameArg(strCommand, fErr)
  2059.     If fErr Then GoTo BadCommandLine
  2060.         
  2061.     ' Both the app removal logfile and executable must exist
  2062.     If Not FileExists(strAppRemovalLog) Then
  2063.         GoTo BadAppRemovalLog
  2064.     End If
  2065.     
  2066.     If Not FileExists(strAppRemovalEXE) Then
  2067.         GoTo BadAppRemovalEXE
  2068.     End If
  2069.     
  2070.     ' Last check:  There should be nothing else on the command line
  2071.     strCommand = Trim$(strCommand)
  2072.     If strCommand <> "" Then
  2073.         GoTo BadCommandLine
  2074.     End If
  2075.     
  2076.     Exit Sub
  2077.     
  2078. BadAppRemovalLog:
  2079.     MsgError ResolveResString(resCANTFINDAPPREMOVALLOG, "|1", strAppRemovalLog), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2080.     ExitSetup frmSetup1, gintRET_FATAL
  2081.     
  2082. BadAppRemovalEXE:
  2083.     MsgError ResolveResString(resCANTFINDAPPREMOVALEXE, "|1", strAppRemovalEXE), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2084.     ExitSetup frmSetup1, gintRET_FATAL
  2085.     
  2086. BadCommandLine:
  2087.     MsgError ResolveResString(resBADCOMMANDLINE), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2088.     ExitSetup frmSetup1, gintRET_FATAL
  2089. End Sub
  2090.  
  2091. '-----------------------------------------------------------
  2092. ' FUNCTION: GetDrivesAllocUnit
  2093. '
  2094. ' Gets the minimum file size allocation unit for the
  2095. ' specified drive
  2096. '
  2097. ' IN: [strDrive] - Drive to get allocation unit for
  2098. '
  2099. ' Returns: minimum allocation unit of drive, or -1 if
  2100. '          this value couldn't be determined
  2101. '-----------------------------------------------------------
  2102. '
  2103. Function GetDrivesAllocUnit(ByVal strDrive As String) As Long
  2104.     Dim strCurDrive As String
  2105.     Dim lAllocUnit As Long
  2106.  
  2107.     On Error Resume Next
  2108.  
  2109.     '
  2110.     'Save current drive
  2111.     '
  2112.     strCurDrive = Left$(CurDir$, 2)
  2113.  
  2114.     '
  2115.     'append a colon to the end of the drivespec if none supplied
  2116.     '
  2117.     If InStr(strDrive, gstrCOLON) = 0 Or Len(strDrive) > 2 Then
  2118.         strDrive = Left$(strDrive, 1) & gstrCOLON
  2119.     End If
  2120.  
  2121.     '
  2122.     'Change to the drive to determine the allocation unit for.  The AllocUnit()
  2123.     'API returns this value for the current drive only
  2124.     '
  2125.     ChDrive strDrive
  2126.  
  2127.     '
  2128.     'If there was an error accessing the specified drive, flag error return.
  2129.     'It is also possible for the AllocUnit() API to return -1 on other failure
  2130.     '
  2131.     If Err <> 0 Or (strDrive <> Left$(CurDir$, 2)) Then
  2132.         lAllocUnit = -1
  2133.     Else
  2134.         lAllocUnit = AllocUnit()
  2135.         If Err <> 0 Then
  2136.             lAllocUnit = -1
  2137.         End If
  2138.     End If
  2139.  
  2140.     If lAllocUnit = -1 Then
  2141.         MsgError Error$ & LS$ & ResolveResString(resALLOCUNIT) & strDrive, MB_ICONEXCLAMATION, gstrTitle
  2142.         If gfSMS Then
  2143.             ExitSetup frmSetup1, gintRET_FATAL
  2144.         End If
  2145.     End If
  2146.  
  2147.     GetDrivesAllocUnit = lAllocUnit
  2148.  
  2149.     '
  2150.     'Restore to original drive
  2151.     '
  2152.     ChDrive strCurDrive
  2153.  
  2154.     Err = 0
  2155. End Function
  2156.  
  2157. '-----------------------------------------------------------
  2158. ' FUNCTION: GetFileName
  2159. '
  2160. ' Return the filename portion of a path
  2161. '
  2162. '-----------------------------------------------------------
  2163. '
  2164. Function GetFileName(ByVal strPath As String) As String
  2165.     Dim strFilename As String
  2166.     Dim iSep As Integer
  2167.     
  2168.     strFilename = strPath
  2169.     Do
  2170.         iSep = InStr(strFilename, gstrSEP_DIR)
  2171.         If iSep = 0 Then iSep = InStr(strFilename, gstrCOLON)
  2172.         If iSep = 0 Then
  2173.             GetFileName = strFilename
  2174.             Exit Function
  2175.         Else
  2176.             strFilename = Right(strFilename, Len(strFilename) - iSep)
  2177.         End If
  2178.     Loop
  2179. End Function
  2180.  
  2181. '-----------------------------------------------------------
  2182. ' FUNCTION: GetFileSize
  2183. '
  2184. ' Determine the size (in bytes) of the specified file
  2185. '
  2186. ' IN: [strFileName] - name of file to get size of
  2187. '
  2188. ' Returns: size of file in bytes, or -1 if an error occurs
  2189. '-----------------------------------------------------------
  2190. '
  2191. Function GetFileSize(strFilename As String) As Long
  2192.     On Error Resume Next
  2193.  
  2194.     GetFileSize = FileLen(strFilename)
  2195.  
  2196.     If Err > 0 Then
  2197.         GetFileSize = -1
  2198.         Err = 0
  2199.     End If
  2200. End Function
  2201.  
  2202. '-----------------------------------------------------------
  2203. ' FUNCTION: GetAppRemovalCmdLine
  2204. '
  2205. ' Returns the correct command-line arguments (including
  2206. ' path to the executable for use in calling the
  2207. ' application removal executable)
  2208. '
  2209. ' IN: [strAppRemovalEXE] - Full path/filename of the app removal EXE
  2210. '     [strAppRemovalLog] - Full path/filename of the app removal logfile
  2211. '     [strSilentLog] - Full path/filename of the file to log messages to when in silent mode.
  2212. '                       If this is an empty string then silent mode is turned off for uninstall.
  2213. '     [fSMS] - Boolean.  If True, we have been doing an SMS install and must tell the Uninstaller
  2214. '              to also do an SMS uninstall.  SMS is the Microsoft Systems Management Server.
  2215. '     [nErrorLevel] - Error level:
  2216. '                        APPREMERR_NONE - no error
  2217. '                        APPREMERR_FATAL - fatal error
  2218. '                        APPREMERR_NONFATAL - non-fatal error, user chose to abort
  2219. '                        APPREMERR_USERCANCEL - user chose to cancel (no error)
  2220. '     [fWaitForParent] - True if the application removal utility should wait
  2221. '                        for the parent (this process) to finish before starting
  2222. '                        to remove files.  Otherwise it may not be able to remove
  2223. '                        this process' executable file, depending upon timing.
  2224. '                        Defaults to False if not specified.
  2225. '-----------------------------------------------------------
  2226. '
  2227. Function GetAppRemovalCmdLine(ByVal strAppRemovalEXE As String, ByVal strAppRemovalLog, ByVal strSilentLog As String, ByVal fSMS As Boolean, ByVal nErrorLevel As Integer, Optional fWaitForParent)
  2228.     Dim strEXE As String
  2229.     Dim strLog As String
  2230.     Dim strSilent As String
  2231.     Dim strErrLevel As String
  2232.     Dim strForce As String
  2233.     Dim strWait As String
  2234.     Dim strSMS As String
  2235.  
  2236.     If IsMissing(fWaitForParent) Then
  2237.         fWaitForParent = False
  2238.     End If
  2239.     
  2240.     strEXE = AddQuotesToFN(strAppRemovalEXE)
  2241.     strLog = "-n " & """" & GetLongPathName(strAppRemovalLog) & """"
  2242.     If gfSilent And strSilentLog <> gstrNULL Then
  2243.         strSilent = "/s " & """" & strSilentLog & """"
  2244.     Else
  2245.         strSilent = gstrNULL
  2246.     End If
  2247.     
  2248.     strSMS = IIf(fSMS, " /q ", gstrNULL)
  2249.     
  2250.     strErrLevel = IIf(nErrorLevel <> APPREMERR_NONE, "-e " & Format(nErrorLevel), "")
  2251.     If nErrorLevel <> APPREMERR_NONE Then
  2252.         strForce = " -f"
  2253.     End If
  2254.     If fWaitForParent Then
  2255.         Dim curProcessId As Currency
  2256.         Dim Wrap As Currency
  2257.         Dim lProcessId As Long
  2258.         Dim cProcessId As Currency
  2259.         
  2260.         Wrap = 2 * (CCur(&H7FFFFFFF) + 1)
  2261.  
  2262.         'Always print as an unsigned long
  2263.         lProcessId = GetCurrentProcessId()
  2264.         cProcessId = lProcessId
  2265.         If cProcessId < 0 Then cProcessId = cProcessId + Wrap
  2266.  
  2267.         strWait = " -w " & str(cProcessId)
  2268.     End If
  2269.     
  2270.     GetAppRemovalCmdLine = strEXE & " " & strLog & " " & strSilent & " " & strSMS & strErrLevel & strForce & strWait
  2271. End Function
  2272.  
  2273. '-----------------------------------------------------------
  2274. ' FUNCTION: IncrementRefCount
  2275. '
  2276. ' Increments the reference count on a file in the registry
  2277. ' so that it may properly be removed if the user chooses
  2278. ' to remove this application.
  2279. '
  2280. ' IN: [strFullPath] - FULL path/filename of the file
  2281. '     [fFileAlreadyExisted] - indicates whether the given
  2282. '                             file already existed on the
  2283. '                             hard drive
  2284. '-----------------------------------------------------------
  2285. '
  2286. Sub IncrementRefCount(ByVal strFullPath As String, ByVal fFileAlreadyExisted As Boolean)
  2287.     Dim strSharedDLLsKey As String
  2288.     strSharedDLLsKey = RegPathWinCurrentVersion() & "\SharedDLLs"
  2289.     
  2290.     'We must always use the LFN for the filename, so that we can uniquely
  2291.     'and accurately identify the file in the registry.
  2292.     strFullPath = GetLongPathName(strFullPath)
  2293.     
  2294.     'Get the current reference count for this file
  2295.     Dim fSuccess As Boolean
  2296.     Dim hKey As Long
  2297.     fSuccess = RegCreateKey(HKEY_LOCAL_MACHINE, strSharedDLLsKey, "", hKey)
  2298.     If fSuccess Then
  2299.         Dim lCurRefCount As Long
  2300.         If Not RegQueryRefCount(hKey, strFullPath, lCurRefCount) Then
  2301.             'No current reference count for this file
  2302.             If fFileAlreadyExisted Then
  2303.                 'If there was no reference count, but the file was found
  2304.                 'on the hard drive, it means one of two things:
  2305.                 '  1) This file is shipped with the operating system
  2306.                 '  2) This file was installed by an older setup program
  2307.                 '     that does not do reference counting
  2308.                 'In either case, the correct conservative thing to do
  2309.                 'is assume that the file is needed by some application,
  2310.                 'which means it should have a reference count of at
  2311.                 'least 1.  This way, our application removal program
  2312.                 'will not delete this file.
  2313.                 lCurRefCount = 1
  2314.  
  2315.             Else
  2316.                 lCurRefCount = 0
  2317.             End If
  2318.         End If
  2319.         
  2320.         'Increment the count in the registry
  2321.         fSuccess = RegSetNumericValue(hKey, strFullPath, lCurRefCount + 1, False)
  2322.         If Not fSuccess Then
  2323.             GoTo DoErr
  2324.         End If
  2325.         RegCloseKey hKey
  2326.     Else
  2327.         GoTo DoErr
  2328.     End If
  2329.     
  2330.     Exit Sub
  2331.     
  2332. DoErr:
  2333.     'An error message should have already been shown to the user
  2334.     Exit Sub
  2335. End Sub
  2336.  
  2337. '-----------------------------------------------------------
  2338. ' FUNCTION: InitDiskInfo
  2339. '
  2340. ' Called before calculating disk space to initialize
  2341. ' values used/determined when calculating disk space
  2342. ' required.
  2343. '-----------------------------------------------------------
  2344. '
  2345. Sub InitDiskInfo()
  2346.     '
  2347.     'Initialize "table" of drives used and disk space array
  2348.     '
  2349.     gstrDrivesUsed = gstrNULL
  2350.     Erase gsDiskSpace
  2351.  
  2352.     mlTotalToCopy = 0
  2353.  
  2354.     '
  2355.     'Get drive/directory for temporary files
  2356.     '
  2357.     mstrConcatDrive = UCase$(Environ$(gstrTMP_DIR))
  2358.     If mstrConcatDrive = gstrNULL Then
  2359.         mstrConcatDrive = UCase$(Environ$(gstrTEMP_DIR))
  2360.     End If
  2361.     AddDirSep mstrConcatDrive
  2362.  
  2363.     If mstrConcatDrive <> gstrNULL Then
  2364.         If CheckDrive(mstrConcatDrive, ResolveResString(resTEMPDRIVE)) = False Then
  2365.             mstrConcatDrive = gstrNULL
  2366.         Else
  2367.             '
  2368.             'If we found a temp drive and the drive is "ready", add it to the
  2369.             'table of drives used
  2370.             '
  2371.             gstrDrivesUsed = Left$(mstrConcatDrive, 1)
  2372.             ReDim Preserve gsDiskSpace(1)
  2373.             gsDiskSpace(1).lAvail = GetDiskSpaceFree(mstrConcatDrive)
  2374.             gsDiskSpace(1).lMinAlloc = GetDrivesAllocUnit(mstrConcatDrive)
  2375.         End If
  2376.     End If
  2377. End Sub
  2378.  
  2379. '-----------------------------------------------------------
  2380. ' FUNCTION: IsDisplayNameUnique
  2381. '
  2382. ' Determines whether a given display name for registering
  2383. '   the application removal executable is unique or not.  This
  2384. '   display name is the title which is presented to the
  2385. '   user in Windows 95's control panel Add/Remove Programs
  2386. '   applet.
  2387. '
  2388. ' IN: [hkeyAppRemoval] - open key to the path in the registry
  2389. '                       containing application removal entries
  2390. '     [strDisplayName] - the display name to test for uniqueness
  2391. '
  2392. ' Returns: True if the given display name is already in use,
  2393. '          False if otherwise
  2394. '-----------------------------------------------------------
  2395. '
  2396. Function IsDisplayNameUnique(ByVal hkeyAppRemoval As Long, ByVal strDisplayName As String) As Boolean
  2397.     Dim lIdx As Long
  2398.     Dim strSubkey As String
  2399.     Dim strDisplayNameExisting As String
  2400.     Const strKEY_DISPLAYNAME$ = "DisplayName"
  2401.     
  2402.     IsDisplayNameUnique = True
  2403.     
  2404.     lIdx = 0
  2405.     Do
  2406.         Select Case RegEnumKey(hkeyAppRemoval, lIdx, strSubkey)
  2407.             Case ERROR_NO_MORE_ITEMS
  2408.                 'No more keys - must be unique
  2409.                 Exit Do
  2410.             Case ERROR_SUCCESS
  2411.                 'We have a key to some application removal program.  Compare its
  2412.                 '  display name with ours
  2413.                 Dim hkeyExisting As Long
  2414.                 
  2415.                 If RegOpenKey(hkeyAppRemoval, strSubkey, hkeyExisting) Then
  2416.                     If RegQueryStringValue(hkeyExisting, strKEY_DISPLAYNAME, strDisplayNameExisting) Then
  2417.                         If strDisplayNameExisting = strDisplayName Then
  2418.                             'There is a match to an existing display name
  2419.                             IsDisplayNameUnique = False
  2420.                             RegCloseKey hkeyExisting
  2421.                             Exit Do
  2422.                         End If
  2423.                     End If
  2424.                     RegCloseKey hkeyExisting
  2425.                 End If
  2426.             Case Else
  2427.                 'Error, we must assume it's unique.  An error will probably
  2428.                 '  occur later when trying to add to the registry
  2429.                 Exit Do
  2430.             'End Case
  2431.         End Select
  2432.         lIdx = lIdx + 1
  2433.     Loop
  2434. End Function
  2435.  
  2436. '-----------------------------------------------------------
  2437. ' FUNCTION: IsNewerVer
  2438. '
  2439. ' Compares two file version structures and determines
  2440. ' whether the source file version is newer (greater) than
  2441. ' the destination file version.  This is used to determine
  2442. ' whether a file needs to be installed or not
  2443. '
  2444. ' IN: [sSrcVer] - source file version information
  2445. '     [sDestVer] - dest file version information
  2446. '
  2447. ' Returns: True if source file is newer than dest file,
  2448. '          False if otherwise
  2449. '-----------------------------------------------------------
  2450. '
  2451. Function IsNewerVer(sSrcVer As VERINFO, sDestVer As VERINFO) As Integer
  2452.     IsNewerVer = False
  2453.  
  2454.     If sSrcVer.nMSHi > sDestVer.nMSHi Then GoTo INVNewer
  2455.     If sSrcVer.nMSHi < sDestVer.nMSHi Then GoTo INVOlder
  2456.     
  2457.     If sSrcVer.nMSLo > sDestVer.nMSLo Then GoTo INVNewer
  2458.     If sSrcVer.nMSLo < sDestVer.nMSLo Then GoTo INVOlder
  2459.     
  2460.     If sSrcVer.nLSHi > sDestVer.nLSHi Then GoTo INVNewer
  2461.     If sSrcVer.nLSHi < sDestVer.nLSHi Then GoTo INVOlder
  2462.     
  2463.     If sSrcVer.nLSLo > sDestVer.nLSLo Then GoTo INVNewer
  2464.  
  2465.     GoTo INVOlder
  2466.  
  2467. INVNewer:
  2468.     IsNewerVer = True
  2469. INVOlder:
  2470. End Function
  2471.  
  2472. '-----------------------------------------------------------
  2473. ' FUNCTION: IsValidDestDir
  2474. '
  2475. ' Determines whether or not the destination directory
  2476. ' specifed in the "DefaultDir" key of the [Setup] section
  2477. ' in SETUP.LST or a destination dir entered by the user
  2478. ' is not a subdirectory of the source directory.
  2479. '
  2480. ' Notes: [gstrSrcPath] - points to the source directory
  2481. '        [strDestDir] - points to the dest directory
  2482. '
  2483. ' Returns: True if dest dir is a valid location, False
  2484. '          otherwise
  2485. '-----------------------------------------------------------
  2486. '
  2487. Function IsValidDestDir(strDestDir As String) As Integer
  2488.     Dim strMsg As String
  2489.  
  2490.     '
  2491.     ' Both of these paths, strDestDir and gstrSrcPath, are *always*
  2492.     ' in the format 'X:\' or 'X:\DIRNAME\'.
  2493.     '
  2494.     If InStr(strDestDir, gstrSrcPath) > 0 Then
  2495.         IsValidDestDir = False
  2496.         strMsg = ResolveResString(resDIRSPECIFIED) & LF$ & strDestDir & LF$ & ResolveResString(resSAMEASSRC)
  2497.         MsgFunc strMsg, MB_OK Or MB_ICONEXCLAMATION, gstrTitle
  2498.     Else
  2499.         IsValidDestDir = True
  2500.     End If
  2501. End Function
  2502.  
  2503. '-----------------------------------------------------------
  2504. ' FUNCTION: MakePath
  2505. '
  2506. ' Creates the specified directory path
  2507. '
  2508. ' IN: [strDirName] - name of the dir path to make
  2509. '     [fAllowIgnore] - whether or not to allow the user to
  2510. '                      ignore any encountered errors.  If
  2511. '                      false, the function only returns
  2512. '                      if successful.  If missing, this
  2513. '                      defaults to True.
  2514. '
  2515. ' Returns: True if successful, False if error and the user
  2516. '          chose to ignore.  (The function does not return
  2517. '          if the user selects ABORT/CANCEL on an error.)
  2518. '-----------------------------------------------------------
  2519. '
  2520. Public Function MakePath(ByVal strDir As String, Optional ByVal fAllowIgnore) As Boolean
  2521.     If IsMissing(fAllowIgnore) Then
  2522.         fAllowIgnore = True
  2523.     End If
  2524.     
  2525.     Do
  2526.         If MakePathAux(strDir) Then
  2527.             MakePath = True
  2528.             Exit Function
  2529.         Else
  2530.             Dim strMsg As String
  2531.             Dim iRet As Integer
  2532.             
  2533.             strMsg = ResolveResString(resMAKEDIR) & LF$ & strDir
  2534.             iRet = MsgError(strMsg, IIf(fAllowIgnore, MB_ABORTRETRYIGNORE, MB_RETRYCANCEL) Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrSETMSG)
  2535.             '
  2536.             ' if we are running silent then we
  2537.             ' can't continue.  Previous MsgError
  2538.             ' took care of write silent log entry.
  2539.             '
  2540.             If gfNoUserInput = True Then
  2541.                 ExitSetup frmCopy, gintRET_FATAL
  2542.             End If
  2543.             
  2544.             Select Case iRet
  2545.                 Case IDABORT, IDCANCEL
  2546.                     ExitSetup frmCopy, gintRET_ABORT
  2547.                 Case IDIGNORE
  2548.                     MakePath = False
  2549.                     Exit Function
  2550.                 Case IDRETRY
  2551.                 'End Case
  2552.             End Select
  2553.         End If
  2554.     Loop
  2555. End Function
  2556.  
  2557. '----------------------------------------------------------
  2558. ' SUB: MoveAppRemovalFiles
  2559. '
  2560. ' Moves the app removal logfile to the application directory,
  2561. ' and registers the app removal executable with the operating
  2562. ' system.
  2563. '----------------------------------------------------------
  2564. Sub MoveAppRemovalFiles(ByVal strGroupName As String)
  2565.     Dim strNewAppRemovalLogName As String
  2566.     
  2567.     'Find a unique name for the app removal logfile in the
  2568.     'application directory
  2569.     
  2570.     '...First try the default extension
  2571.     strNewAppRemovalLogName = gstrDestDir & mstrFILE_APPREMOVALLOGBASE & mstrFILE_APPREMOVALLOGEXT
  2572.     If FileExists(strNewAppRemovalLogName) Then
  2573.         '...Next try incrementing integral extensions
  2574.         Dim iExt As Integer
  2575.         Do
  2576.             If iExt > 999 Then
  2577.                 GoTo CopyErr
  2578.             End If
  2579.             
  2580.  
  2581.             strNewAppRemovalLogName = gstrDestDir & mstrFILE_APPREMOVALLOGBASE & gstrSEP_EXT & Format(iExt, "000")
  2582.             If Not FileExists(strNewAppRemovalLogName) Then
  2583.                 Exit Do 'Unique name was found
  2584.             Else
  2585.                 iExt = iExt + 1
  2586.             End If
  2587.         Loop
  2588.     End If
  2589.     
  2590.     
  2591.     
  2592.     On Error GoTo CopyErr
  2593.     FileCopy gstrAppRemovalLog, strNewAppRemovalLogName
  2594.     
  2595.     'Now we need to start logging in the new logfile, so that the
  2596.     'creation of the application removal icon under NT gets logged.
  2597.     EnableLogging strNewAppRemovalLogName
  2598.     
  2599.     On Error GoTo 0
  2600.     If Not RegisterAppRemovalEXE(gstrAppRemovalEXE, strNewAppRemovalLogName, strGroupName) Then
  2601.         If TreatAsWin95() Then
  2602.             MsgError ResolveResString(resCANTREGISTERAPPREMOVER), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2603.         Else
  2604.             MsgError ResolveResString(resCANTCREATEAPPREMOVALICON), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2605.         End If
  2606.         ExitSetup frmSetup1, gintRET_FATAL
  2607.     End If
  2608.     
  2609.     'Now we can delete the original logfile, since we no longer have a reference
  2610.     'to it, and start using the new logfile
  2611.     On Error Resume Next
  2612.     Kill gstrAppRemovalLog
  2613.     
  2614.     'This temporary app removal logfile should no longer be used
  2615.     gstrAppRemovalLog = strNewAppRemovalLogName
  2616.     gfAppRemovalFilesMoved = True
  2617.     
  2618.     Exit Sub
  2619.     
  2620. CleanUpOnErr:
  2621.     On Error Resume Next
  2622.     Kill strNewAppRemovalLogName
  2623.     On Error GoTo 0
  2624.     MsgError ResolveResString(resCANTCOPYLOG, "|1", gstrAppRemovalLog), vbExclamation Or vbOKOnly, gstrTitle
  2625.     ExitSetup Screen.ActiveForm, gintRET_FATAL
  2626.     
  2627. CopyErr:
  2628.     Resume CleanUpOnErr
  2629. End Sub
  2630.  
  2631. '-----------------------------------------------------------
  2632. ' FUNCTION: OpenConcatFile
  2633. '
  2634. ' Opens a file to be the destination for concatenation of
  2635. ' two or more source files that (typically) have been
  2636. ' split across disks.
  2637. '
  2638. ' Returns: The handle of the file to use for concatentation
  2639. '          if the open was successful, or -1 if the open
  2640. '          failed and the user chose to ignore the error.
  2641. '-----------------------------------------------------------
  2642. '
  2643. Function OpenConcatFile() As Integer
  2644.     Dim intFileNum As Integer
  2645.     Dim strMsg As String
  2646.  
  2647.     On Error Resume Next
  2648.  
  2649.     Do
  2650.         Kill mstrConcatDrive & mstrCONCATFILE
  2651.         Err = 0
  2652.  
  2653.         intFileNum = FreeFile
  2654.         Open mstrConcatDrive & mstrCONCATFILE For Binary Access Write As intFileNum
  2655.  
  2656.         If Err > 0 Then
  2657.             strMsg = ResolveResString(resNOCREATE) & LS$ & mstrConcatDrive & mstrCONCATFILE
  2658.             strMsg = strMsg & LS$ & ResolveResString(resNOTPROTECT)
  2659.             Select Case MsgError(strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or IIf(gfNoUserInput, vbDefaultButton1, MB_DEFBUTTON2), gstrSETMSG)
  2660.                 Case IDABORT
  2661.                     ExitSetup frmCopy, gintRET_ABORT
  2662.                 Case IDIGNORE
  2663.                     OpenConcatFile = -1
  2664.                     Exit Function
  2665.                 'End Case
  2666.             End Select
  2667.         End If
  2668.     Loop While Err > 0
  2669.  
  2670.     OpenConcatFile = intFileNum
  2671. End Function
  2672.  
  2673. '-----------------------------------------------------------
  2674. ' SUB: ParseDateTime
  2675. '
  2676. ' Same as CDate with a string argument, except that it
  2677. ' ignores the current localization settings.  This is
  2678. ' important because SETUP.LST always uses the same
  2679. ' format for dates.
  2680. '
  2681. ' IN: [strDate] - string representing the date in
  2682. '                 the format mm/dd/yy or mm/dd/yyyy
  2683. ' OUT: The date which strDate represents
  2684. '-----------------------------------------------------------
  2685. '
  2686. Function ParseDateTime(ByVal strDateTime As String) As Date
  2687.     Const strDATESEP$ = "/"
  2688.     Const strTIMESEP$ = ":"
  2689.     Const strDATETIMESEP$ = " "
  2690.     Dim iMonth As Integer
  2691.     Dim iDay As Integer
  2692.     Dim iYear As Integer
  2693.     Dim iHour As Integer
  2694.     Dim iMinute As Integer
  2695.     Dim iSecond As Integer
  2696.     Dim iPos As Integer
  2697.     Dim vTime As Date
  2698.     
  2699.     iPos = InStr(strDateTime, strDATESEP)
  2700.     If iPos = 0 Then GoTo Err
  2701.     iMonth = Val(Left$(strDateTime, iPos - 1))
  2702.     strDateTime = Mid$(strDateTime, iPos + 1)
  2703.     
  2704.     iPos = InStr(strDateTime, strDATESEP)
  2705.     If iPos = 0 Then GoTo Err
  2706.     iDay = Val(Left$(strDateTime, iPos - 1))
  2707.     strDateTime = Mid$(strDateTime, iPos + 1)
  2708.     
  2709.     iPos = InStr(strDateTime, strDATETIMESEP)
  2710.     If iPos = 0 Then GoTo SkipTime
  2711.     iYear = Val(Left$(strDateTime, iPos - 1))
  2712.     strDateTime = Mid$(strDateTime, iPos + 1)
  2713.     
  2714.     vTime = TimeSerial(0, 0, 0)
  2715.     
  2716.     iPos = InStr(strDateTime, strTIMESEP)
  2717.     If iPos = 0 Then GoTo SkipTime
  2718.     iHour = Val(Left$(strDateTime, iPos - 1))
  2719.     strDateTime = Mid$(strDateTime, iPos + 1)
  2720.     
  2721.     iPos = InStr(strDateTime, strTIMESEP)
  2722.     If iPos = 0 Then GoTo SkipTime
  2723.     iMinute = Val(Left$(strDateTime, iPos - 1))
  2724.     strDateTime = Mid$(strDateTime, iPos + 1)
  2725.     
  2726.     iSecond = Val(strDateTime)
  2727.     
  2728.     vTime = TimeSerial(iHour, iMinute, iSecond)
  2729.     
  2730. SkipTime:
  2731.     
  2732.     If iYear < 100 Then iYear = iYear + 1900
  2733.     
  2734.     ParseDateTime = DateSerial(iYear, iMonth, iDay) + vTime
  2735.     
  2736.     Exit Function
  2737.     
  2738. Err:
  2739.     Error 13 'Type mismatch error, same as intrinsic CDate triggers on error
  2740. End Function
  2741.  
  2742. '-----------------------------------------------------------
  2743. ' SUB: PerformDDE
  2744. '
  2745. ' Performs a Program Manager DDE operation as specified
  2746. ' by the intDDE flag and the passed in parameters.
  2747. ' Possible operations are:
  2748. '
  2749. '   mintDDE_ITEMADD:  Add an icon to the active group
  2750. '   mintDDE_GRPADD:   Create a program manager group
  2751. '
  2752. ' IN: [frm] - form containing a label named 'lblDDE'
  2753. '     [strGroup] - name of group to create or insert icon
  2754. '     [strTitle] - title of icon or group
  2755. '     [strCmd] - command line for icon/item to add
  2756. '     [intDDE] - ProgMan DDE action to perform
  2757. '-----------------------------------------------------------
  2758. '
  2759. Sub PerformDDE(frm As Form, ByVal strGroup As String, ByVal strCmd As String, ByVal strTitle As String, ByVal intDDE As Integer, ByVal fLog As Boolean)
  2760.     Const strCOMMA$ = ","
  2761.     Const strRESTORE$ = ", 1)]"
  2762.     Const strACTIVATE$ = ", 5)]"
  2763.     Const strENDCMD$ = ")]"
  2764.     Const strSHOWGRP$ = "[ShowGroup("
  2765.     Const strADDGRP$ = "[CreateGroup("
  2766.     Const strREPLITEM$ = "[ReplaceItem("
  2767.     Const strADDITEM$ = "[AddItem("
  2768.  
  2769.     Dim intIdx As Integer        'loop variable
  2770.  
  2771.     SetMousePtr gintMOUSE_HOURGLASS
  2772.  
  2773.     '
  2774.     'Initialize for DDE Conversation with Windows Program Manager in
  2775.     'manual mode (.LinkMode = 2) where destination control is not auto-
  2776.     'matically updated.  Set DDE timeout for 10 seconds.  The loop around
  2777.     'DoEvents() is to allow time for the DDE Execute to be processsed.
  2778.     '
  2779.  
  2780.     Dim intRetry As Integer
  2781.     For intRetry = 1 To 20
  2782.         On Error Resume Next
  2783.         frm.lblDDE.LinkTopic = "PROGMAN|PROGMAN"
  2784.         If Err = 0 Then
  2785.             Exit For
  2786.         End If
  2787.         DoEvents
  2788.     Next intRetry
  2789.         
  2790.     frm.lblDDE.LinkMode = 2
  2791.     For intIdx = 1 To 10
  2792.       DoEvents
  2793.     Next
  2794.     frm.lblDDE.LinkTimeout = 100
  2795.  
  2796.     On Error Resume Next
  2797.  
  2798.     If Err = 0 Then
  2799.         Select Case intDDE
  2800.             Case mintDDE_ITEMADD
  2801.                 '
  2802.                 ' The item will be created in the group titled strGroup
  2803.                 '
  2804.                 ' Write the action to the logfile
  2805.                 '
  2806.                 If fLog Then
  2807.                     NewAction gstrKEY_PROGMANITEM, """" & strUnQuoteString(strGroup) & """" & ", " & """" & strUnQuoteString(strTitle) & """"
  2808.                 End If
  2809.                 '
  2810.                 ' Force the group strGroup to be the active group.  Additem only
  2811.                 ' puts icons in the active group.
  2812.                 '
  2813.                 #If 0 Then
  2814.                     frm.lblDDE.LinkExecute strSHOWGRP & strGroup & strACTIVATE
  2815.                 #Else
  2816.                     ' BUG #5-30466,stephwe,10/96: strShowGRP doesn't seem to work if ProgMan is minimized.
  2817.                     '  : strADDGRP does the trick fine, though, and it doesn't matter if it already exists.
  2818.                     frm.lblDDE.LinkExecute strADDGRP & strGroup & strENDCMD
  2819.                 #End If
  2820.                 frm.lblDDE.LinkExecute strREPLITEM & strTitle & strENDCMD
  2821.                 Err = 0
  2822.                 frm.lblDDE.LinkExecute strADDITEM & strCmd & strCOMMA & strTitle & String$(3, strCOMMA) & strENDCMD
  2823.             Case mintDDE_GRPADD
  2824.                 '
  2825.                 ' Write the action to the logfile
  2826.                 '
  2827.                 If fLog Then
  2828.                     NewAction gstrKEY_PROGMANGROUP, """" & strUnQuoteString(strGroup) & """"
  2829.                 End If
  2830.                 frm.lblDDE.LinkExecute strADDGRP & strGroup & strENDCMD
  2831.                 frm.lblDDE.LinkExecute strSHOWGRP & strGroup & strRESTORE
  2832.             'End Case
  2833.         End Select
  2834.     End If
  2835.  
  2836.     
  2837.     '
  2838.     'Disconnect DDE Link
  2839.     '
  2840.  
  2841.     frm.lblDDE.LinkMode = 0
  2842.     frm.lblDDE.LinkTopic = ""
  2843.  
  2844.  
  2845.     SetMousePtr gintMOUSE_DEFAULT
  2846.  
  2847.     If fLog Then
  2848.         CommitAction
  2849.     End If
  2850.     
  2851.     
  2852.     Err = 0
  2853. End Sub
  2854.  
  2855. '-----------------------------------------------------------
  2856. ' SUB: PromptForNextDisk
  2857. '
  2858. ' If the source media is removable or a network connection,
  2859. ' prompts the user to insert the specified disk number
  2860. ' containing the filename which is used to determine that
  2861. ' the correct disk is inserted.
  2862. '
  2863. ' IN: [intDiskNum] - disk number to insert
  2864. '     [strDetectFile] - file to search for to ensure that
  2865. '                       the correct disk was inserted
  2866. '
  2867. ' Notes: [gstrSrcPath] - used to identify the source drive
  2868. '-----------------------------------------------------------
  2869. '
  2870. Sub PromptForNextDisk(ByVal intDiskNum As Integer, ByVal strDetectFile As String)
  2871.     Static intDrvType As Integer
  2872.  
  2873.     Dim intRC As Integer
  2874.     Dim strMsg As String
  2875.     Dim strDrive As String
  2876.     Dim strMultDirBaseName As String
  2877.     Dim strDetectPath As String
  2878.  
  2879.     On Error Resume Next
  2880.  
  2881.     strMultDirBaseName = ResolveResString(resCOMMON_MULTDIRBASENAME)
  2882.     '
  2883.     'Get source drive and, if we haven't yet determined it, get the
  2884.     'source drive type
  2885.     '
  2886.     
  2887.     strDrive = Left$(gstrSrcPath, 2)
  2888.     If intDrvType = 0 Then
  2889.         If IsUNCName(strDrive) Then
  2890.             intDrvType = intDRIVE_REMOTE
  2891.             strDrive = gstrSrcPath
  2892.         Else
  2893.             intDrvType = GetDriveType(Asc(strDrive) - 65)
  2894.         End If
  2895.     End If
  2896.  
  2897.     While SrcFileMissing(gstrSrcPath, strDetectFile, intDiskNum) = True
  2898.         Select Case intDrvType
  2899.             Case 0, intDRIVE_REMOVABLE, intDRIVE_CDROM
  2900.                 strMsg = ResolveResString(resINSERT) & LF$ & ResolveResString(resDISK) & Format$(intDiskNum)
  2901.                 strMsg = strMsg & ResolveResString(resINTO) & strDrive
  2902.             Case intDRIVE_REMOTE
  2903.                 strMsg = ResolveResString(resCHKCONNECT) & strDrive
  2904.             Case intDRIVE_FIXED
  2905.                 If DirExists(gstrSrcPath & strMultDirBaseName & Format(intDiskNum)) = True Then
  2906.                     strDetectPath = gstrSrcPath & strMultDirBaseName & Format(intDiskNum)
  2907.                 Else
  2908.                     strDetectPath = gstrSrcPath
  2909.                 End If
  2910.                 strMsg = ResolveResString(resCOMMON_CANTFINDSRCFILE, "|1", strDetectPath & gstrSEP_DIR & strDetectFile)
  2911.             'End Case
  2912.         End Select
  2913.  
  2914.         Beep
  2915.         intRC = MsgFunc(strMsg, MB_OKCANCEL Or MB_ICONEXCLAMATION, gstrSETMSG)
  2916.         '
  2917.         ' We should always fail if in silent or sms mode.
  2918.         '
  2919.         If intRC = IDCANCEL Or gfNoUserInput Then
  2920.             ExitSetup frmCopy, gintRET_EXIT
  2921.         End If
  2922.     Wend
  2923.  
  2924.     gintCurrentDisk = intDiskNum
  2925. End Sub
  2926. Function SrcFileMissing(ByVal strSrcDir As String, ByVal strSrcFile As String, ByVal intDiskNum As Integer) As Boolean
  2927. '-----------------------------------------------------------
  2928. ' FUNCTION: SrcFileMissing
  2929. '
  2930. ' Tries to locate the file strSrcFile by first looking
  2931. ' in the strSrcDir directory, then in the DISK(x+1)
  2932. ' directory if it exists.
  2933. '
  2934. ' IN: [strSrcDir] - Directory/Path where file should be.
  2935. '     [strSrcFile] - File we are looking for.
  2936. '     [intDiskNum] - Disk number we are expecting file
  2937. '                    to be on.
  2938. '
  2939. ' Returns: True if file not found; otherwise, false
  2940. '-----------------------------------------------------------
  2941.     Dim fFound As Boolean
  2942.     Dim strMultDirBaseName As String
  2943.     
  2944.     fFound = False
  2945.     
  2946.     AddDirSep strSrcDir
  2947.     '
  2948.     ' First check to see if it's in the main src directory.
  2949.     ' This would happen if someone copied the contents of
  2950.     ' all the floppy disks to a single directory on the
  2951.     ' hard drive.  We should allow this to work.
  2952.     '
  2953.     ' This test would also let us know if the user inserted
  2954.     ' the wrong floppy disk or if a network connection is
  2955.     ' unavailable.
  2956.     '
  2957.     If FileExists(strSrcDir & strSrcFile) = True Then
  2958.         fFound = True
  2959.         GoTo doneSFM
  2960.     End If
  2961.     '
  2962.     ' Next try the DISK(x) subdirectory of the main src
  2963.     ' directory.  This would happen if the floppy disks
  2964.     ' were copied into directories named DISK1, DISK2,
  2965.     ' DISK3,..., DISKN, etc.
  2966.     '
  2967.     strMultDirBaseName = ResolveResString(resCOMMON_MULTDIRBASENAME)
  2968.     If FileExists(strSrcDir & ".." & gstrSEP_DIR & strMultDirBaseName & Format(intDiskNum) & gstrSEP_DIR & strSrcFile) = True Then
  2969.         fFound = True
  2970.         GoTo doneSFM
  2971.     End If
  2972.     
  2973. doneSFM:
  2974.     SrcFileMissing = Not fFound
  2975. End Function
  2976. '-----------------------------------------------------------
  2977. ' FUNCTION: ReadIniFile
  2978. '
  2979. ' Reads a value from the specified section/key of the
  2980. ' specified .INI file
  2981. '
  2982. ' IN: [strIniFile] - name of .INI file to read
  2983. '     [strSection] - section where key is found
  2984. '     [strKey] - name of key to get the value of
  2985. '
  2986. ' Returns: non-zero terminated value of .INI file key
  2987. '-----------------------------------------------------------
  2988. '
  2989. Function ReadIniFile(ByVal strIniFile As String, ByVal strSection As String, ByVal strKey As String) As String
  2990.     Dim strBuffer As String
  2991.     Dim intPos As Integer
  2992.  
  2993.     '
  2994.     'If successful read of .INI file, strip any trailing zero returned by the Windows API GetPrivateProfileString
  2995.     '
  2996.     strBuffer = Space$(gintMAX_SIZE)
  2997.     
  2998.     If GetPrivateProfileString(strSection, strKey, gstrNULL, strBuffer, gintMAX_SIZE, strIniFile) > 0 Then
  2999.         ReadIniFile = RTrim$(StripTerminator(strBuffer))
  3000.     Else
  3001.         ReadIniFile = gstrNULL
  3002.     End If
  3003. End Function
  3004.  
  3005. '-----------------------------------------------------------
  3006. ' SUB: ReadSetupFileLine
  3007. '
  3008. ' Reads the requested 'FileX=' key from the specified
  3009. ' section of the setup information file (SETUP.LST).
  3010. '
  3011. ' IN: [strSection] - name of section to read from SETUP.LST,
  3012. '                    Ex: "Files"
  3013. '     [intFileNum] - file number index to read
  3014. '
  3015. ' OUT: [sFile] - FILEINFO Type variable that, after parsing,
  3016. '                holds the information for the file
  3017. '                described.
  3018. '
  3019. ' Returns: True if the requested info was successfully read,
  3020. '          False otherwise
  3021. '
  3022. ' Notes: Lines in the setup information file have the
  3023. '        following format:
  3024. '
  3025. '        #,[SPLIT],SrcName,DestName,DestDir,Register,
  3026. '        Date,Size,Version
  3027. '
  3028. '        [#] - disk number where this file is located
  3029. '        [SPLIT] - optional, determines whether this is
  3030. '                  an extent of a split file.  The last
  3031. '                  extent does not specify this key
  3032. '        [SrcName] - filename on the installation media
  3033. '        [DestName] - file name to use when copied
  3034. '
  3035. '        (For split files, the following info is required only
  3036. '        for the *first* extent)
  3037. '
  3038. '        [DestDir] - dirname or macro specifying destdir
  3039. '        [Register] - reginfo file name or macro specifying
  3040. '                     file registration action
  3041. '        [Date] - date of the source file
  3042. '        [Size] - size of the source file
  3043. '        [Version] - optional, version number string
  3044. '        [Reserved] - Must be empty, else error!
  3045. '        [ProgIcon] - Caption for icon, if there is one.
  3046. '        [ProgCmdLine] - Command line for icon, if there is one.
  3047. '-----------------------------------------------------------
  3048. '
  3049. Function ReadSetupFileLine(ByVal strSection As String, ByVal intFileNum As Integer, sFile As FILEINFO) As Integer
  3050.     Static strSplitName As String
  3051.     Const CompareBinary = 0
  3052.  
  3053.     Dim strLine As String
  3054.     Dim strMsg As String
  3055.     Dim intOffset As Integer
  3056.     Dim intAnchor As Integer
  3057.     Dim fDone As Integer
  3058.     Dim fErr As Boolean
  3059.     Dim strVersion As String
  3060.     Dim strFilename As String
  3061.  
  3062.     ReadSetupFileLine = False
  3063.  
  3064.     sFile.fSystem = False
  3065.     sFile.fShared = False
  3066.     
  3067.     '
  3068.     ' Read the requested line, if unable to read it (strLine = gstrNULL) then exit
  3069.     '
  3070.     strLine = ReadIniFile(gstrSetupInfoFile, strSection, gstrINI_FILE & Format$(intFileNum))
  3071.     If strLine = gstrNULL Then
  3072.         Exit Function
  3073.     End If
  3074.  
  3075.     '
  3076.     ' Get the disk number
  3077.     '
  3078.     intOffset = intGetNextFldOffset(1, strLine, gstrCOMMA, CompareBinary)
  3079.     sFile.intDiskNum = Val(Left$(strLine, intOffset - 1))
  3080.     If sFile.intDiskNum < 1 Then
  3081.         GoTo RSFLError
  3082.     End If
  3083.  
  3084.     '
  3085.     'Is this a split file extent (other than the last extent of a split file)
  3086.     '
  3087.     intAnchor = intOffset + 1
  3088.     
  3089.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3090.     If intOffset > 0 Then
  3091.         sFile.fSplit = IIf(Mid$(strLine, intAnchor, intOffset - intAnchor) = gstrNULL, False, True)
  3092.     Else
  3093.         GoTo RSFLError
  3094.     End If
  3095.  
  3096.     '
  3097.     'source file name, ensure it's not a UNC name
  3098.     '
  3099.     intAnchor = intOffset + 1
  3100.     sFile.strSrcName = strExtractFilenameItem(strLine, intAnchor, fErr)
  3101.     If fErr Then GoTo RSFLError
  3102.     If IsUNCName(sFile.strSrcName) = True Then GoTo RSFLError
  3103.     intAnchor = intAnchor + 1 'Skip past the comma
  3104.     
  3105.     '
  3106.     'dest file name, ensure it's not a UNC name
  3107.     '
  3108.     sFile.strDestName = strExtractFilenameItem(strLine, intAnchor, fErr)
  3109.     If fErr Then GoTo RSFLError
  3110.     If IsUNCName(sFile.strDestName) = True Then GoTo RSFLError
  3111.     If Mid$(strLine, intAnchor, 1) = gstrCOMMA Then
  3112.         If IsUNCName(sFile.strDestName) = True Then
  3113.             GoTo RSFLError
  3114.         End If
  3115.         intAnchor = intAnchor + 1 'Skip past the comma
  3116.     Else
  3117.         '
  3118.         'If no list separator after the dest file name, then this should be a
  3119.         'split file extent
  3120.         '
  3121.         If strSplitName = gstrNULL Then
  3122.             GoTo RSFLError
  3123.         Else
  3124.             sFile.strDestDir = gstrNULL
  3125.             fDone = True
  3126.         End If
  3127.     End If
  3128.     
  3129.     strFilename = GetFileName(sFile.strDestName)
  3130.  
  3131.     '
  3132.     'Ensure that SPLIT files in SETUP.LST are ended properly by checking that all dest
  3133.     'file names after the first SPLIT line are identical, up to and including the
  3134.     'dest file name of the very next occurring *non* SPLIT line.
  3135.     '
  3136.     If sFile.fSplit = True Then
  3137.         If strSplitName = gstrNULL Then
  3138.             strSplitName = sFile.strDestName
  3139.         Else
  3140.             If strSplitName <> sFile.strDestName Then
  3141.                 GoTo RSFLError
  3142.             End If
  3143.         End If
  3144.     Else
  3145.         If strSplitName <> gstrNULL And strSplitName <> sFile.strDestName Then
  3146.             GoTo RSFLError
  3147.         Else
  3148.             strSplitName = gstrNULL
  3149.  
  3150.         End If
  3151.     End If
  3152.  
  3153.     If fDone = True Then
  3154.         GoTo RSFLDone
  3155.     End If
  3156.  
  3157.     '
  3158.     'parse and resolve destination directory
  3159.     '
  3160.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3161.     If intOffset > 0 Then
  3162.         Dim strInitialDestDir As String
  3163.         strInitialDestDir = Mid$(strLine, intAnchor, intOffset - intAnchor)
  3164.         If InStr(strInitialDestDir, gstrWINSYSDESTSYSFILE) Then
  3165.             sFile.fSystem = True
  3166.         End If
  3167.         If InStr(strInitialDestDir, gstrDAODEST) Then
  3168.             '
  3169.             ' Special case for DAO destinations.  If there
  3170.             ' are any DAO files, we need to add special
  3171.             ' DAO reg info later.  gfRegDAO tells us to do that.
  3172.             '
  3173.             gfRegDAO = True
  3174.         End If
  3175.         sFile.strDestDir = ResolveDestDir(strInitialDestDir)
  3176.         If sFile.strDestDir <> "?" Then
  3177.             sFile.strDestDir = ResolveDir(sFile.strDestDir, False, False)
  3178.             If sFile.strDestDir = gstrNULL Or IsUNCName(sFile.strDestDir) Then
  3179.                 GoTo RSFLError
  3180.             End If
  3181.         End If
  3182.     Else
  3183.         GoTo RSFLError
  3184.     End If
  3185.  
  3186.     '
  3187.     'file registration information
  3188.     '
  3189.     intAnchor = intOffset + 1
  3190.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3191.     If intOffset > 0 Then
  3192.         sFile.strRegister = Mid$(strLine, intAnchor, intOffset - intAnchor)
  3193.     Else
  3194.         GoTo RSFLError
  3195.     End If
  3196.  
  3197.     '
  3198.     'Extract file share type
  3199.     '
  3200.     intAnchor = intOffset + 1
  3201.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3202.     sFile.fShared = False
  3203.     If intOffset > 0 Then
  3204.         Dim strShareType As String
  3205.         strShareType = Mid$(strLine, intAnchor, intOffset - intAnchor)
  3206.         Select Case strShareType
  3207.             Case mstrPRIVATEFILE
  3208.                 sFile.fShared = False
  3209.             Case mstrSHAREDFILE
  3210.                 If sFile.fSystem Then
  3211.                     'A file cannot be both system and shared
  3212.                     GoTo RSFLError
  3213.                 End If
  3214.                 
  3215.                 sFile.fShared = True
  3216.             Case Else
  3217.                 GoTo RSFLError
  3218.             'End Case
  3219.         End Select
  3220.     End If
  3221.     
  3222.     '
  3223.     'Extract file date and convert to a date variant
  3224.     '
  3225.     intAnchor = intOffset + 1
  3226.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3227.     If intOffset > 0 Then
  3228.         If IsDate(Mid$(strLine, intAnchor, intOffset - intAnchor)) = True Then
  3229.             sFile.varDate = ParseDateTime(Mid$(strLine, intAnchor, intOffset - intAnchor))
  3230.         Else
  3231.             GoTo RSFLError
  3232.         End If
  3233.     End If
  3234.  
  3235.     '
  3236.     'Get file size
  3237.     '
  3238.     intAnchor = intOffset + 1
  3239.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3240.     If intOffset > 0 Then
  3241.         sFile.lFileSize = Val(Mid$(strLine, intAnchor, intOffset - intAnchor))
  3242.     Else
  3243.         GoTo RSFLError
  3244.     End If
  3245.  
  3246.     '
  3247.     ' Get the version number, otherwise flag that there is no version info
  3248.     '
  3249.     intAnchor = intOffset + 1
  3250.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3251.     If intOffset > 0 Then
  3252.         strVersion = Trim(Mid$(strLine, intAnchor, intOffset - intAnchor))
  3253.         If strVersion = "" Then
  3254.             sFile.sVerInfo.nMSHi = gintNOVERINFO
  3255.         Else
  3256.             PackVerInfo strVersion, sFile.sVerInfo
  3257.         End If
  3258.     Else
  3259.         GoTo RSFLError
  3260.     End If
  3261.     
  3262.     '
  3263.     ' The next field is reserved for a future release.
  3264.     ' If it contains any information, an error will occur.
  3265.     ' Using this field for any other purpose may cause
  3266.     ' setup to fail in future versions of Visual Basic.
  3267.     '
  3268.     intAnchor = intOffset + 1
  3269.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3270.     If intOffset > 0 Then
  3271.         If Len(strUnQuoteString(Mid(strLine, intAnchor, intOffset - intAnchor))) > 0 Then
  3272.             GoTo RSFLError
  3273.         End If
  3274.     Else
  3275.         GoTo RSFLError
  3276.     End If
  3277.     '
  3278.     ' Get the caption for the program's icon, if there is one.
  3279.     '
  3280.     intAnchor = intOffset + 1
  3281.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3282.     '
  3283.     ' Ignore this field for RacMgr.EXE and AutMgr32.EXE.  They are handled
  3284.     ' differently.  See Form_Load()
  3285.     '
  3286.     If strFilename = mstrFILE_RACMGR32 Or strFilename = mstrFILE_AUTMGR32 Then
  3287.         sFile.strProgramIconTitle = ""
  3288.     Else
  3289.         If intOffset > 0 Then
  3290.             sFile.strProgramIconTitle = Trim(Mid(strLine, intAnchor, intOffset - intAnchor))
  3291.         Else
  3292.             GoTo RSFLError
  3293.         End If
  3294.     End If
  3295.     '
  3296.     ' Get the Command Line for the program's icon, if there is one.  Note,
  3297.     ' that this is the last item in the list.  There should be no comma
  3298.     ' after this item but we check in case there is.
  3299.     '
  3300.     intAnchor = intOffset + 1
  3301.     intOffset = intGetNextFldOffset(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3302.     '
  3303.     ' Ignore this field for RacMgr.EXE and AutMgr32.EXE.  They are handled
  3304.     ' differently.  See Form_Load()
  3305.     '
  3306.     If strFilename = mstrFILE_RACMGR32 Or strFilename = mstrFILE_AUTMGR32 Then
  3307.         sFile.strProgramIconCmdLine = ""
  3308.     Else
  3309.         If intOffset > 0 Then
  3310.             '
  3311.             ' There is a comma at the end of this field.  Use it.
  3312.             '
  3313.             sFile.strProgramIconCmdLine = Trim(Mid(strLine, intAnchor, intOffset - intAnchor))
  3314.         Else
  3315.             sFile.strProgramIconCmdLine = Trim(Mid(strLine, intAnchor))
  3316.         End If
  3317.     End If
  3318.     
  3319. RSFLDone:
  3320.     ReadSetupFileLine = True
  3321.     Exit Function
  3322.  
  3323. RSFLError:
  3324.     strMsg = gstrSetupInfoFile & LS$ & ResolveResString(resINVLINE) & LS$
  3325.     strMsg = strMsg & ResolveResString(resSECTNAME) & strSection & LF$ & strLine
  3326.     MsgError strMsg, MB_ICONSTOP, gstrTitle
  3327.     ExitSetup frmSetup1, gintRET_FATAL
  3328. End Function
  3329.  
  3330. '-----------------------------------------------------------
  3331. ' SUB: ReadSetupRemoteLine
  3332. '
  3333. ' Reads the requested 'RemoteX=' key from the specified
  3334. ' section of the setup information file (SETUP.LST).
  3335. '
  3336. ' IN: [strSection] - name of section to read from SETUP.LST,
  3337. '                    Ex: "Files"
  3338. '     [intFileNum] - remote number index to read
  3339. '
  3340. ' OUT: [rInfo] - REGINFO Type variable that, after parsing,
  3341. '                holds the information for the line
  3342. '                described.
  3343. '
  3344. ' Returns: True if the requested info was successfully read,
  3345. '          False otherwise
  3346. '
  3347. ' Notes: Remote server lines in the setup information file
  3348. '        have the following format:
  3349. '
  3350. '        address,protocol,authentication-level
  3351. '
  3352. '        [address] - network address of the server, if known
  3353. '        [protocol] - network protocol name, if known
  3354. '        [authentication level] - authentication level (or 0 for default)
  3355. '-----------------------------------------------------------
  3356. '
  3357. Function ReadSetupRemoteLine(ByVal strSection As String, ByVal intFileNum As Integer, rInfo As REGINFO) As Integer
  3358.     Dim strLine As String
  3359.     Dim strMsg As String
  3360.     Dim intAnchor As Integer
  3361.     Dim intOffset As Integer
  3362.     Dim fErr As Boolean
  3363.  
  3364.     ReadSetupRemoteLine = False
  3365.  
  3366.     '
  3367.     'Read the requested line, if unable to read it (strLine = gstrNULL) then exit
  3368.     '
  3369.     strLine = ReadIniFile(gstrSetupInfoFile, strSection, gstrINI_REMOTE & Format$(intFileNum))
  3370.     If strLine = gstrNULL Then
  3371.         Exit Function
  3372.     End If
  3373.  
  3374.     '
  3375.     'Get the network address
  3376.     '
  3377.     intAnchor = 1
  3378.     fErr = False
  3379.     If Mid$(strLine, intAnchor, 1) = gstrCOMMA Then
  3380.         rInfo.strNetworkAddress = ""
  3381.     Else
  3382.         rInfo.strNetworkAddress = strExtractFilenameItem(strLine, intAnchor, fErr)
  3383.     End If
  3384.     If fErr Then GoTo RSRLError
  3385.     intAnchor = intAnchor + 1 'Skip past the comma
  3386.  
  3387.     '
  3388.     'Get the network protocol
  3389.     '
  3390.     If Mid$(strLine, intAnchor, 1) = gstrCOMMA Then
  3391.         rInfo.strNetworkProtocol = ""
  3392.     Else
  3393.         rInfo.strNetworkProtocol = strExtractFilenameItem(strLine, intAnchor, fErr)
  3394.     End If
  3395.     If fErr Then GoTo RSRLError
  3396.     intAnchor = intAnchor + 1 'Skip past the comma
  3397.  
  3398.     '
  3399.     'Get the authentication level (must be a single digit
  3400.     '  in the range 0..6)
  3401.     '
  3402.     Const intMaxAuthentication = 6
  3403.     Dim strAuthentication As String
  3404.     
  3405.     strAuthentication = Mid$(strLine, intAnchor, 1)
  3406.     If Len(strAuthentication) <> 1 Then GoTo RSRLError
  3407.     If (Asc(strAuthentication) < Asc("0")) Or (Asc(strAuthentication) > Asc("9")) Then GoTo RSRLError
  3408.     rInfo.intAuthentication = Val(strAuthentication)
  3409.     If rInfo.intAuthentication > intMaxAuthentication Then GoTo RSRLError
  3410.     '
  3411.     ' Is this dcom or remote automation?
  3412.     '
  3413.     intAnchor = InStr(intAnchor + 1, strLine, gstrCOMMA)
  3414.     If intAnchor > 0 Then
  3415.         rInfo.fDCOM = (Trim(Mid$(strLine, intAnchor + 1)) = gstrDCOM)
  3416.     End If
  3417.     
  3418.     ReadSetupRemoteLine = True
  3419.     Exit Function
  3420.  
  3421. RSRLError:
  3422.     strMsg = gstrSetupInfoFile & LS$ & ResolveResString(resINVLINE) & LS$
  3423.     strMsg = strMsg & ResolveResString(resSECTNAME) & strSection & LF$ & strLine
  3424.     MsgError strMsg, MB_ICONSTOP, gstrTitle
  3425.     ExitSetup frmSetup1, gintRET_FATAL
  3426. End Function
  3427.  
  3428. '-----------------------------------------------------------
  3429. ' FUNCTION: RegCloseKey
  3430. '
  3431. ' Closes an open registry key.
  3432. '
  3433. ' Returns: True on success, else False.
  3434. '-----------------------------------------------------------
  3435. '
  3436. Function RegCloseKey(ByVal hKey As Long) As Boolean
  3437.     Dim lResult As Long
  3438.     
  3439.     On Error GoTo 0
  3440.     lResult = OSRegCloseKey(hKey)
  3441.     RegCloseKey = (lResult = ERROR_SUCCESS)
  3442. End Function
  3443.  
  3444. '-----------------------------------------------------------
  3445. ' FUNCTION: RegCreateKey
  3446. '
  3447. ' Opens (creates if already exists) a key in the system registry.
  3448. '
  3449. ' IN: [hkey] - The HKEY parent.
  3450. '     [lpszSubKeyPermanent] - The first part of the subkey of
  3451. '         'hkey' that will be created or opened.  The application
  3452. '         removal utility (32-bit only) will never delete any part
  3453. '         of this subkey.  May NOT be an empty string ("").
  3454. '     [lpszSubKeyRemovable] - The subkey of hkey\lpszSubKeyPermanent
  3455. '         that will be created or opened.  If the application is
  3456. '         removed (32-bit only), then this entire subtree will be
  3457. '         deleted, if it is empty at the time of application removal.
  3458. '         If this parameter is an empty string (""), then the entry
  3459. '         will not be logged.
  3460. '
  3461. ' OUT: [phkResult] - The HKEY of the newly-created or -opened key.
  3462. '
  3463. ' Returns: True if the key was created/opened OK, False otherwise
  3464. '   Upon success, phkResult is set to the handle of the key.
  3465. '
  3466. '-----------------------------------------------------------
  3467. Function RegCreateKey(ByVal hKey As Long, ByVal lpszSubKeyPermanent As String, ByVal lpszSubKeyRemovable As String, phkResult As Long) As Boolean
  3468.     Dim lResult As Long
  3469.     Dim strHkey As String
  3470.     Dim fLog As Boolean
  3471.     Dim strSubKeyFull As String
  3472.  
  3473.     On Error GoTo 0
  3474.  
  3475.     If lpszSubKeyPermanent = "" Then
  3476.         RegCreateKey = False 'Error: lpszSubKeyPermanent must not = ""
  3477.         Exit Function
  3478.     End If
  3479.     
  3480.     If Left$(lpszSubKeyRemovable, 1) = "\" Then
  3481.         lpszSubKeyRemovable = Mid$(lpszSubKeyRemovable, 2)
  3482.     End If
  3483.  
  3484.     If lpszSubKeyRemovable = "" Then
  3485.         fLog = False
  3486.     Else
  3487.         fLog = True
  3488.     End If
  3489.     
  3490.     If lpszSubKeyRemovable <> "" Then
  3491.         strSubKeyFull = lpszSubKeyPermanent & "\" & lpszSubKeyRemovable
  3492.     Else
  3493.         strSubKeyFull = lpszSubKeyPermanent
  3494.     End If
  3495.     strHkey = strGetHKEYString(hKey)
  3496.  
  3497.     If fLog Then
  3498.         NewAction _
  3499.           gstrKEY_REGKEY, _
  3500.           """" & strHkey & "\" & lpszSubKeyPermanent & """" _
  3501.             & ", " & """" & lpszSubKeyRemovable & """"
  3502.     End If
  3503.  
  3504.     lResult = OSRegCreateKey(hKey, strSubKeyFull, phkResult)
  3505.     If lResult = ERROR_SUCCESS Then
  3506.         RegCreateKey = True
  3507.         If fLog Then
  3508.             CommitAction
  3509.         End If
  3510.         AddHkeyToCache phkResult, strHkey & "\" & strSubKeyFull
  3511.     Else
  3512.         RegCreateKey = False
  3513.         MsgError ResolveResString(resERR_REG), vbOKOnly Or vbExclamation, gstrTitle
  3514.         If fLog Then
  3515.             AbortAction
  3516.         End If
  3517.         If gfNoUserInput Then
  3518.             ExitSetup frmSetup1, gintRET_FATAL
  3519.         End If
  3520.     End If
  3521. End Function
  3522.  
  3523. '-----------------------------------------------------------
  3524. ' FUNCTION: RegDeleteKey
  3525. '
  3526. ' Deletes an existing key in the system registry.
  3527. '
  3528. ' Returns: True on success, False otherwise
  3529. '-----------------------------------------------------------
  3530. '
  3531. Function RegDeleteKey(ByVal hKey As Long, ByVal lpszSubKey As String) As Boolean
  3532.     Dim lResult As Long
  3533.     
  3534.     On Error GoTo 0
  3535.     lResult = OSRegDeleteKey(hKey, lpszSubKey)
  3536.     RegDeleteKey = (lResult = ERROR_SUCCESS)
  3537. End Function
  3538.  
  3539. '-----------------------------------------------------------
  3540. ' SUB: RegEdit
  3541. '
  3542. ' Calls REGEDIT to add the information in the specifed file
  3543. ' to the system registry.  If your .REG file requires path
  3544. ' information based upon the destination directory given by
  3545. ' the user, then you will need to write and call a .REG fixup
  3546. ' routine before performing the registration below.
  3547. '
  3548. ' WARNING: Use of this functionality under Win32 is not recommended,
  3549. ' WARNING: because the application removal utility does not support
  3550. ' WARNING: undoing changes that occur as a result of calling
  3551. ' WARNING: REGEDIT on an arbitrary .REG file.
  3552. ' WARNING: Instead, it is recommended that you use the RegCreateKey(),
  3553. ' WARNING: RegOpenKey(), RegSetStringValue(), etc. functions in
  3554. ' WARNING: this module instead.  These make entries to the
  3555. ' WARNING: application removal logfile, thus enabling application
  3556. ' WARNING: removal to undo such changes.
  3557. '
  3558. ' IN: [strRegFile] - name of file containing reg. info
  3559. '-----------------------------------------------------------
  3560. '
  3561. Sub RegEdit(ByVal strRegFile As String)
  3562.     Const strREGEDIT$ = "REGEDIT /S "
  3563.  
  3564.     Dim fShellOK As Integer
  3565.  
  3566.     On Error Resume Next
  3567.  
  3568.     If FileExists(strRegFile) = True Then
  3569.         'Because regedit is a 16-bit application, it does not accept
  3570.         'double quotes around the filename.  Thus, if strRegFile
  3571.         'contains spaces, the only way to get this to work is to pass
  3572.         'regedit the short pathname version of the filename.
  3573.         strRegFile = GetShortPathName(strRegFile)
  3574.         
  3575.         fShellOK = FSyncShell(strREGEDIT & strRegFile, 7)
  3576.         frmSetup1.Refresh
  3577.     Else
  3578.         MsgError ResolveResString(resCANTFINDREGFILE, "|1", strRegFile), vbExclamation Or vbOKOnly, gstrTitle
  3579.         ExitSetup frmSetup1, gintRET_FATAL
  3580.     End If
  3581.  
  3582.     Err = 0
  3583. End Sub
  3584.  
  3585. ' FUNCTION: RegEnumKey
  3586. '
  3587. ' Enumerates through the subkeys of an open registry
  3588. ' key (returns the "i"th subkey of hkey, if it exists)
  3589. '
  3590. ' Returns:
  3591. '   ERROR_SUCCESS on success.  strSubkeyName is set to the name of the subkey.
  3592. '   ERROR_NO_MORE_ITEMS if there are no more subkeys (32-bit only)
  3593. '   anything else - error
  3594. '
  3595. Function RegEnumKey(ByVal hKey As Long, ByVal i As Long, strKeyName As String) As Long
  3596.     Dim strResult As String
  3597.     
  3598.     strResult = String(300, " ")
  3599.     RegEnumKey = OSRegEnumKey(hKey, i, strResult, Len(strResult))
  3600.     strKeyName = StripTerminator(strResult)
  3601. End Function
  3602. '-----------------------------------------------------------
  3603. ' SUB: RegisterDAO
  3604. '
  3605. ' Special keys need to be added to the registry if
  3606. ' DAO is installed.  This routine adds those keys.
  3607. '
  3608. ' Note, these keys will not be uninstalled.
  3609. '
  3610. Sub RegisterDAO()
  3611.     Const strDAOKey = "CLSID\{F7A9C6E0-EFF2-101A-8185-00DD01108C6B}"
  3612.     Const strDAOKeyVal = "OLE 2.0 Link"
  3613.     Const strDAOInprocHandlerKey = "CLSID\{F7A9C6E0-EFF2-101A-8185-00DD01108C6B}\InprocHandler"
  3614.     Const strDAOInprocHandlerKeyVal = "ole2.dll"
  3615.     Const strDAOProgIDKey = "CLSID\{F7A9C6E0-EFF2-101A-8185-00DD01108C6B}\ProgID"
  3616.     Const strDAOProgIDKeyVal = "Access.OLE2Link"
  3617.     
  3618.     Dim hKey As Long
  3619.     
  3620.     If Not RegCreateKey(HKEY_CLASSES_ROOT, strDAOKey, "", hKey) Then
  3621.         '
  3622.         ' RegCreateKey displays an error if something goes wrong.
  3623.         '
  3624.         GoTo REGDAOError
  3625.     End If
  3626.     '
  3627.     ' Set the key's value
  3628.     '
  3629.     If Not RegSetStringValue(hKey, "", strDAOKeyVal, False) Then
  3630.         '
  3631.         ' RegSetStringValue displays an error if something goes wrong.
  3632.         '
  3633.         GoTo REGDAOError
  3634.     End If
  3635.     '
  3636.     ' Close the key
  3637.     '
  3638.     RegCloseKey hKey
  3639.     '
  3640.     ' Repeat the same process for the other two keys.
  3641.     '
  3642.     If Not RegCreateKey(HKEY_CLASSES_ROOT, strDAOInprocHandlerKey, "", hKey) Then GoTo REGDAOError
  3643.     If Not RegSetStringValue(hKey, "", strDAOInprocHandlerKeyVal, False) Then GoTo REGDAOError
  3644.     RegCloseKey hKey
  3645.     
  3646.     If Not RegCreateKey(HKEY_CLASSES_ROOT, strDAOProgIDKey, "", hKey) Then GoTo REGDAOError
  3647.     If Not RegSetStringValue(hKey, "", strDAOProgIDKeyVal, False) Then GoTo REGDAOError
  3648.     RegCloseKey hKey
  3649.  
  3650.     Exit Sub
  3651.         
  3652. REGDAOError:
  3653.     '
  3654.     ' Error messages should have already been displayed.
  3655.     '
  3656.     ExitSetup frmSetup1, gintRET_FATAL
  3657.         
  3658. End Sub
  3659. '-----------------------------------------------------------
  3660. ' SUB: RegisterFiles
  3661. '
  3662. ' Loop through the list (array) of files to register that
  3663. ' was created in the CopySection function and register
  3664. ' each file therein as required
  3665. '
  3666. ' Notes: msRegInfo() array created by CopySection function
  3667. '-----------------------------------------------------------
  3668. '
  3669. Sub RegisterFiles()
  3670.     Const strEXT_EXE$ = "EXE"
  3671.  
  3672.     Dim intIdx As Integer
  3673.     Dim intLastIdx As Integer
  3674.     Dim strFilename As String
  3675.     Dim strMsg As String
  3676.  
  3677.     On Error Resume Next
  3678.  
  3679.     '
  3680.     'Get number of items to register, if none then we can get out of here
  3681.     '
  3682.     intLastIdx = UBound(msRegInfo)
  3683.     If Err > 0 Then
  3684.         GoTo RFCleanup
  3685.     End If
  3686.  
  3687.     For intIdx = 0 To intLastIdx
  3688.         strFilename = msRegInfo(intIdx).strFilename
  3689.  
  3690.         Select Case msRegInfo(intIdx).strRegister
  3691.             Case mstrDLLSELFREGISTER
  3692.                 Dim intDllSelfRegRet As Integer
  3693.                 Dim intErrRes As Integer
  3694.                 Const FAIL_OLE = 2
  3695.                 Const FAIL_LOAD = 3
  3696.                 Const FAIL_ENTRY = 4
  3697.                 Const FAIL_REG = 5
  3698.                 
  3699.                 NewAction gstrKEY_DLLSELFREGISTER, """" & strFilename & """"
  3700.                 
  3701. RetryDllSelfReg:
  3702.                 Err = 0
  3703.                 intErrRes = 0
  3704.                 intDllSelfRegRet = DLLSelfRegister(strFilename)
  3705.                 If Err Then
  3706.                     intErrRes = resCOMMON_CANTREGUNEXPECTED
  3707.                 Else
  3708.                     Select Case intDllSelfRegRet
  3709.                         Case 0
  3710.                             'Good - everything's okay
  3711.                         Case FAIL_OLE
  3712.                             intErrRes = resCOMMON_CANTREGOLE
  3713.                         Case FAIL_LOAD
  3714.                             intErrRes = resCOMMON_CANTREGLOAD
  3715.                         Case FAIL_ENTRY
  3716.                             intErrRes = resCOMMON_CANTREGENTRY
  3717.                         Case FAIL_REG
  3718.                             intErrRes = resCOMMON_CANTREGREG
  3719.                         Case Else
  3720.                             intErrRes = resCOMMON_CANTREGUNEXPECTED
  3721.                         'End Case
  3722.                     End Select
  3723.                 End If
  3724.                 
  3725.                 If intErrRes Then
  3726.                     'There was some kind of error
  3727.                     
  3728.                     'Log the more technical version of the error message -
  3729.                     'this would be too confusing to show to the end user
  3730.                     LogError ResolveResString(intErrRes, "|1", strFilename)
  3731.                     
  3732.                     'Now show a general error message to the user
  3733. AskWhatToDo:
  3734.                     strMsg = ResolveResString(resCOMMON_CANTREG, "|1", strFilename)
  3735.                     
  3736.                     Select Case MsgError(strMsg, vbExclamation Or vbAbortRetryIgnore, gstrTitle)
  3737.                         Case vbAbort:
  3738.                             ExitSetup frmSetup1, gintRET_ABORT
  3739.                             GoTo AskWhatToDo
  3740.                         Case vbRetry:
  3741.                             GoTo RetryDllSelfReg
  3742.                         Case vbIgnore:
  3743.                             AbortAction
  3744.                         'End Case
  3745.                     End Select
  3746.                 Else
  3747.                     CommitAction
  3748.                 End If
  3749.             Case mstrEXESELFREGISTER
  3750.                 '
  3751.                 'Only self register EXE files
  3752.                 '
  3753.                 If Extension(strFilename) = strEXT_EXE Then
  3754.                     NewAction gstrKEY_EXESELFREGISTER, """" & strFilename & """"
  3755.                     Err = 0
  3756.                     ExeSelfRegister strFilename
  3757.                     If Err Then
  3758.                         AbortAction
  3759.                     Else
  3760.                         CommitAction
  3761.                     End If
  3762.                 End If
  3763.             Case mstrREMOTEREGISTER
  3764.                 NewAction gstrKEY_REMOTEREGISTER, """" & strFilename & """"
  3765.                 Err = 0
  3766.                 RemoteRegister strFilename, msRegInfo(intIdx)
  3767.                 If Err Then
  3768.                     AbortAction
  3769.                 Else
  3770.                     CommitAction
  3771.                 End If
  3772.             Case mstrTLBREGISTER
  3773.                 NewAction gstrKEY_TLBREGISTER, """" & strFilename & """"
  3774.                 '
  3775.                 ' Call VB5STKIT.DLL's RegisterTLB export which calls
  3776.                 ' LoadTypeLib and RegisterTypeLib.
  3777.                 '
  3778. RetryTLBReg:
  3779.                 If Not RegisterTLB(strFilename) Then
  3780.                     '
  3781.                     ' Registration of the TLB file failed.
  3782.                     '
  3783.                     LogError ResolveResString(resCOMMON_CANTREGTLB, "|1", strFilename)
  3784. TLBAskWhatToDo:
  3785.                     strMsg = ResolveResString(resCOMMON_CANTREGTLB, "|1", strFilename)
  3786.                     
  3787.                     Select Case MsgError(strMsg, vbExclamation Or vbAbortRetryIgnore, gstrTitle)
  3788.                         Case vbAbort:
  3789.                             ExitSetup frmSetup1, gintRET_ABORT
  3790.                             GoTo TLBAskWhatToDo
  3791.                         Case vbRetry:
  3792.                             GoTo RetryTLBReg
  3793.                         Case vbIgnore:
  3794.                             AbortAction
  3795.                         'End Case
  3796.                     End Select
  3797.                 Else
  3798.                     CommitAction
  3799.                 End If
  3800.             Case mstrVBLREGISTER
  3801.                 '
  3802.                 ' RegisterVBLFile takes care of logging, etc.
  3803.                 '
  3804.  
  3805.                 RegisterVBLFile strFilename
  3806.             Case Else
  3807.                 RegEdit msRegInfo(intIdx).strRegister
  3808.             'End Case
  3809.         End Select
  3810.     Next
  3811.  
  3812.  
  3813.     Erase msRegInfo
  3814.  
  3815. RFCleanup:
  3816.     Err = 0
  3817. End Sub
  3818. '-----------------------------------------------------------
  3819. ' SUB: RegisterLicenses
  3820. '
  3821. ' Find all the setup.lst license entries and register
  3822. ' them.
  3823. '-----------------------------------------------------------
  3824. '
  3825. Sub RegisterLicenses()
  3826.     Const strINI_LICENSES = "Licenses"
  3827.     Dim iLic As Integer
  3828.     Dim strLine As String
  3829.     Dim strLicKey As String
  3830.     Dim strLicVal As String
  3831.     Dim iCommaPos As Integer
  3832.     Dim strMsg As String
  3833.  
  3834.     iLic = 1
  3835.     Do
  3836.         strLine = ReadIniFile(gstrSetupInfoFile, strINI_LICENSES, gstrINI_LICENSE & iLic)
  3837.         If strLine = gstrNULL Then
  3838.             '
  3839.             ' We've got all the licenses.
  3840.             '
  3841.             Exit Sub
  3842.         End If
  3843.         strLine = strUnQuoteString(strLine)
  3844.         '
  3845.         ' We have a license, parse it and register it.
  3846.         '
  3847.         iCommaPos = InStr(strLine, gstrCOMMA)
  3848.         If iCommaPos = 0 Then
  3849.             '
  3850.             ' Looks like the setup.lst file is corrupt.  There should
  3851.             ' always be a comma in the license information that separates
  3852.             ' the license key from the license value.
  3853.             '
  3854.             GoTo RLError
  3855.         End If
  3856.         strLicKey = Left(strLine, iCommaPos - 1)
  3857.         strLicVal = Mid(strLine, iCommaPos + 1)
  3858.         
  3859.         RegisterLicense strLicKey, strLicVal
  3860.         
  3861.         iLic = iLic + 1
  3862.     Loop While strLine <> gstrNULL
  3863.     Exit Sub
  3864.         
  3865. RLError:
  3866.     strMsg = gstrSetupInfoFile & LS$ & ResolveResString(resINVLINE) & LS$
  3867.     strMsg = strMsg & ResolveResString(resSECTNAME) & strINI_LICENSES & LF$ & strLine
  3868.     MsgError strMsg, MB_ICONSTOP, gstrTitle
  3869.     ExitSetup frmSetup1, gintRET_FATAL
  3870. End Sub
  3871. '-----------------------------------------------------------
  3872. ' SUB: RegisterLicense
  3873. '
  3874. ' Register license information given the key and default
  3875. ' value.  License information always goes into
  3876. ' HKEY_CLASSES_ROOT\Licenses.
  3877. '-----------------------------------------------------------
  3878. '
  3879. Sub RegisterLicense(strLicKey As String, strLicVal As String)
  3880.     Const strREG_LICENSES = "Licenses"
  3881.     Dim hKey As Long
  3882.     '
  3883.     ' Create the key
  3884.     '
  3885.     If Not RegCreateKey(HKEY_CLASSES_ROOT, strREG_LICENSES, strLicKey, hKey) Then
  3886.         '
  3887.         ' RegCreateKey displays an error if something goes wrong.
  3888.         '
  3889.         GoTo REGError
  3890.     End If
  3891.     '
  3892.     ' Set the key's value
  3893.     '
  3894.     If Not RegSetStringValue(hKey, "", strLicVal, True) Then
  3895.         '
  3896.         ' RegSetStringValue displays an error if something goes wrong.
  3897.         '
  3898.         GoTo REGError
  3899.     End If
  3900.     '
  3901.     ' Close the key
  3902.     '
  3903.     RegCloseKey hKey
  3904.  
  3905.     Exit Sub
  3906.         
  3907. REGError:
  3908.     '
  3909.     ' Error messages should have already been displayed.
  3910.     '
  3911.     ExitSetup frmSetup1, gintRET_FATAL
  3912. End Sub
  3913. '-----------------------------------------------------------
  3914. ' SUB: RegisterVBLFile
  3915. '
  3916. ' Register license information in a VB License (vbl) file.
  3917. ' Basically, parse out the license info and then call
  3918. ' RegisterLicense.
  3919. '
  3920. ' If strVBLFile is not a valid VBL file, nothing is
  3921. ' registered.
  3922. '-----------------------------------------------------------
  3923. '
  3924. Sub RegisterVBLFile(strVBLFile As String)
  3925.     Dim strLicKey As String
  3926.     Dim strLicVal As String
  3927.     
  3928.     GetLicInfoFromVBL strVBLFile, strLicKey, strLicVal
  3929.     If strLicKey <> gstrNULL Then
  3930.         RegisterLicense strLicKey, strLicVal
  3931.     End If
  3932. End Sub
  3933.  
  3934. '----------------------------------------------------------
  3935. ' SUB: RegisterAppRemovalEXE
  3936. '
  3937. ' Registers the application removal program (Windows 95 only)
  3938. ' or else places an icon for it in the application directory.
  3939. '
  3940. ' Returns True on success, False otherwise.
  3941. '----------------------------------------------------------
  3942. Function RegisterAppRemovalEXE(ByVal strAppRemovalEXE As String, ByVal strAppRemovalLog As String, ByVal strGroupName As String) As Boolean
  3943.     On Error GoTo Err
  3944.     
  3945.     Const strREGSTR_VAL_AppRemoval_APPNAMELINE = "ApplicationName"
  3946.     Const strREGSTR_VAL_AppRemoval_DISPLAYNAME = "DisplayName"
  3947.     Const strREGSTR_VAL_AppRemoval_COMMANDLINE = "UninstallString"
  3948.     Const strREGSTR_VAL_AppRemoval_APPTOUNINSTALL = "AppToUninstall"
  3949.     
  3950.     
  3951.     Dim strREGSTR_PATH_UNINSTALL As String
  3952.     strREGSTR_PATH_UNINSTALL = RegPathWinCurrentVersion() & "\Uninstall"
  3953.     
  3954.     'The command-line for the application removal executable is simply the path
  3955.     'for the installation logfile
  3956.     Dim strAppRemovalCmdLine As String
  3957.     strAppRemovalCmdLine = GetAppRemovalCmdLine(strAppRemovalEXE, strAppRemovalLog, gstrNULL, False, APPREMERR_NONE)
  3958.     '
  3959.     ' Make sure that the Removal command line (including path, filename, commandline args, etc.
  3960.     ' is not longer than the max allowed, which is _MAX_PATH.
  3961.     '
  3962.     If Not fCheckFNLength(strAppRemovalCmdLine) Then
  3963.         Dim strMsg As String
  3964.         strMsg = ResolveResString(resCANTCREATEICONPATHTOOLONG) & LS$ & ResolveResString(resCHOOSENEWDEST) & LS$ & strAppRemovalCmdLine
  3965.         Call MsgError(strMsg, vbOKOnly, gstrSETMSG)
  3966.         ExitSetup frmCopy, gintRET_FATAL
  3967.         Exit Function
  3968.     End If
  3969.     '
  3970.     ' Create registry entries to tell Windows where the app removal executable is,
  3971.     ' how it should be displayed to the user, and what the command-line arguments are
  3972.     '
  3973.     Dim iAppend As Integer
  3974.     Dim fOk As Boolean
  3975.     Dim hkeyAppRemoval As Long
  3976.     Dim hkeyOurs As Long
  3977.     Dim i As Integer
  3978.     
  3979.     'Go ahead and create a key to the main Uninstall branch
  3980.     If Not RegCreateKey(HKEY_LOCAL_MACHINE, strREGSTR_PATH_UNINSTALL, "", hkeyAppRemoval) Then
  3981.         GoTo Err
  3982.     End If
  3983.     
  3984.     'We need a unique key.  This key is never shown to the end user.  We will use a key of
  3985.     'the form 'ST5UNST #xxx'
  3986.     Dim strAppRemovalKey As String
  3987.     Dim strAppRemovalKeyBase As String
  3988.     Dim hkeyTest As Long
  3989.     strAppRemovalKeyBase = mstrFILE_APPREMOVALLOGBASE$ & " #"
  3990.     iAppend = 1
  3991.     
  3992.     Do
  3993.         strAppRemovalKey = strAppRemovalKeyBase & Format(iAppend)
  3994.         If RegOpenKey(hkeyAppRemoval, strAppRemovalKey, hkeyTest) Then
  3995.             'This key already exists.  But we need a unique key.
  3996.             RegCloseKey hkeyTest
  3997.         Else
  3998.             'We've found a key that doesn't already exist.  Use it.
  3999.             Exit Do
  4000.         End If
  4001.         
  4002.         iAppend = iAppend + 1
  4003.     Loop
  4004.     
  4005.     '
  4006.     ' We also need a unique displayname.  This name is
  4007.     ' the only means the user has to identify the application
  4008.     ' to remove
  4009.     '
  4010.     Dim strDisplayName As String
  4011.     strDisplayName = gstrAppName 'First try... Application name
  4012.     If Not IsDisplayNameUnique(hkeyAppRemoval, strDisplayName) Then
  4013.         'Second try... Add path
  4014.         strDisplayName = strDisplayName & " (" & gstrDestDir & ")"
  4015.         If Not IsDisplayNameUnique(hkeyAppRemoval, strDisplayName) Then
  4016.             'Subsequent tries... Append a unique integer
  4017.             Dim strDisplayNameBase As String
  4018.             
  4019.             strDisplayNameBase = strDisplayName
  4020.             iAppend = 3
  4021.             Do
  4022.                 strDisplayName = strDisplayNameBase & " #" & Format(iAppend)
  4023.                 If IsDisplayNameUnique(hkeyAppRemoval, strDisplayName) Then
  4024.                     Exit Do
  4025.                 Else
  4026.                     iAppend = iAppend + 1
  4027.                 End If
  4028.             Loop
  4029.         End If
  4030.     End If
  4031.     
  4032.     'Go ahead and fill in entries for the app removal executable
  4033.     If Not RegCreateKey(hkeyAppRemoval, strAppRemovalKey, "", hkeyOurs) Then
  4034.         GoTo Err
  4035.     End If
  4036.     If Not RegSetStringValue(hkeyOurs, strREGSTR_VAL_AppRemoval_APPNAMELINE, gstrAppExe, False) Then
  4037.         GoTo Err
  4038.     End If
  4039.     If Not RegSetStringValue(hkeyOurs, strREGSTR_VAL_AppRemoval_DISPLAYNAME, strDisplayName, False) Then
  4040.         GoTo Err
  4041.     End If
  4042.     If Not RegSetStringValue(hkeyOurs, strREGSTR_VAL_AppRemoval_COMMANDLINE, strAppRemovalCmdLine, False) Then
  4043.         GoTo Err
  4044.     End If
  4045.         
  4046.     If Not RegSetStringValue(hkeyOurs, strREGSTR_VAL_AppRemoval_APPTOUNINSTALL, gstrAppToUninstall, False) Then
  4047.         GoTo Err
  4048.     End If
  4049.     If Not TreatAsWin95() Then
  4050.         '
  4051.         ' Under NT3.51, we simply place an icon to the app removal EXE in the program manager
  4052.         '
  4053.         If fMainGroupWasCreated Then
  4054.             CreateProgManItem frmSetup1, strGroupName, strAppRemovalCmdLine, ResolveResString(resAPPREMOVALICONNAME, "|1", gstrAppName)
  4055.         Else
  4056.             'If you get this message, it means that you incorrectly customized Form_Load().
  4057.             'Under 32-bits and NT 3.51, a Program Manager group must always be created.
  4058.             MsgError ResolveResString(resNOFOLDERFORICON, "|1", strAppRemovalEXE), MB_OK Or MB_ICONEXCLAMATION, gstrTitle
  4059.             ExitSetup frmSetup1, gintRET_FATAL
  4060.         End If
  4061.     End If
  4062.     
  4063.     RegCloseKey hkeyAppRemoval
  4064.     RegCloseKey hkeyOurs
  4065.     
  4066.     RegisterAppRemovalEXE = True
  4067.     Exit Function
  4068.     
  4069. Err:
  4070.     If hkeyOurs Then
  4071.         RegCloseKey hkeyOurs
  4072.         RegDeleteKey hkeyAppRemoval, strAppRemovalKey
  4073.     End If
  4074.     If hkeyAppRemoval Then
  4075.         RegCloseKey hkeyAppRemoval
  4076.     End If
  4077.     
  4078.     RegisterAppRemovalEXE = False
  4079.     Exit Function
  4080. End Function
  4081.  
  4082. '-----------------------------------------------------------
  4083. ' FUNCTION: RegOpenKey
  4084. '
  4085. ' Opens an existing key in the system registry.
  4086. '
  4087. ' Returns: True if the key was opened OK, False otherwise
  4088. '   Upon success, phkResult is set to the handle of the key.
  4089. '-----------------------------------------------------------
  4090. '
  4091. Function RegOpenKey(ByVal hKey As Long, ByVal lpszSubKey As String, phkResult As Long) As Boolean
  4092.     Dim lResult As Long
  4093.     Dim strHkey As String
  4094.  
  4095.     On Error GoTo 0
  4096.  
  4097.     strHkey = strGetHKEYString(hKey)
  4098.  
  4099.     lResult = OSRegOpenKey(hKey, lpszSubKey, phkResult)
  4100.     If lResult = ERROR_SUCCESS Then
  4101.         RegOpenKey = True
  4102.         AddHkeyToCache phkResult, strHkey & "\" & lpszSubKey
  4103.     Else
  4104.         RegOpenKey = False
  4105.     End If
  4106. End Function
  4107. '----------------------------------------------------------
  4108. ' FUNCTION: RegPathWinPrograms
  4109. '
  4110. ' Returns the name of the registry key
  4111. ' "\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
  4112. '----------------------------------------------------------
  4113. Function RegPathWinPrograms() As String
  4114.     RegPathWinPrograms = RegPathWinCurrentVersion() & "\Explorer\Shell Folders"
  4115. End Function
  4116.  
  4117. '----------------------------------------------------------
  4118. ' FUNCTION: RegPathWinCurrentVersion
  4119. '
  4120. ' Returns the name of the registry key
  4121. ' "\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion"
  4122. '----------------------------------------------------------
  4123. Function RegPathWinCurrentVersion() As String
  4124.     RegPathWinCurrentVersion = "SOFTWARE\Microsoft\Windows\CurrentVersion"
  4125. End Function
  4126.  
  4127. '----------------------------------------------------------
  4128. ' FUNCTION: RegQueryIntValue
  4129. '
  4130. ' Retrieves the integer data for a named
  4131. ' (strValueName = name) or unnamed (strValueName = "")
  4132. ' value within a registry key.  If the named value
  4133. ' exists, but its data is not a REG_DWORD, this function
  4134. ' fails.
  4135. '
  4136. ' NOTE: There is no 16-bit version of this function.
  4137. '
  4138. ' Returns: True on success, else False.
  4139. '   On success, lData is set to the numeric data value
  4140. '
  4141. '----------------------------------------------------------
  4142. Function RegQueryNumericValue(ByVal hKey As Long, ByVal strValueName As String, lData As Long) As Boolean
  4143.     Dim lResult As Long
  4144.     Dim lValueType As Long
  4145.     Dim lBuf As Long
  4146.     Dim lDataBufSize As Long
  4147.     
  4148.     RegQueryNumericValue = False
  4149.     
  4150.     On Error GoTo 0
  4151.     
  4152.     ' Get length/data type
  4153.     lDataBufSize = 4
  4154.         
  4155.     lResult = OSRegQueryValueEx(hKey, strValueName, 0&, lValueType, lBuf, lDataBufSize)
  4156.     If lResult = ERROR_SUCCESS Then
  4157.         If lValueType = REG_DWORD Then
  4158.             lData = lBuf
  4159.             RegQueryNumericValue = True
  4160.         End If
  4161.     End If
  4162. End Function
  4163.  
  4164. ' FUNCTION: RegQueryStringValue
  4165. '
  4166. ' Retrieves the string data for a named
  4167. ' (strValueName = name) or unnamed (strValueName = "")
  4168. ' value within a registry key.  If the named value
  4169. ' exists, but its data is not a string, this function
  4170. ' fails.
  4171. '
  4172. ' NOTE: For 16-bits, strValueName MUST be "" (but the
  4173. ' NOTE: parameter is left in for source code compatability)
  4174. '
  4175. ' Returns: True on success, else False.
  4176. '   On success, strData is set to the string data value
  4177. '
  4178. Function RegQueryStringValue(ByVal hKey As Long, ByVal strValueName As String, strData As String) As Boolean
  4179.     Dim lResult As Long
  4180.     Dim lValueType As Long
  4181.     Dim strBuf As String
  4182.     Dim lDataBufSize As Long
  4183.     
  4184.     RegQueryStringValue = False
  4185.     On Error GoTo 0
  4186.     ' Get length/data type
  4187.     lResult = OSRegQueryValueEx(hKey, strValueName, 0&, lValueType, ByVal 0&, lDataBufSize)
  4188.     If lResult = ERROR_SUCCESS Then
  4189.         If lValueType = REG_SZ Then
  4190.             strBuf = String(lDataBufSize, " ")
  4191.             lResult = OSRegQueryValueEx(hKey, strValueName, 0&, 0&, ByVal strBuf, lDataBufSize)
  4192.             If lResult = ERROR_SUCCESS Then
  4193.                 RegQueryStringValue = True
  4194.                 strData = StripTerminator(strBuf)
  4195.             End If
  4196.         End If
  4197.     End If
  4198. End Function
  4199.  
  4200. '----------------------------------------------------------
  4201. ' FUNCTION: RegQueryRefCount
  4202. '
  4203. ' Retrieves the data inteded as a reference count for a
  4204. ' particular value within a registry key.  Although
  4205. ' REG_DWORD is the preferred way of storing reference
  4206. ' counts, it is possible that some installation programs
  4207. ' may incorrect use a string or binary value instead.
  4208. ' This routine accepts the data whether it is a string,
  4209. ' a binary value or a DWORD (Long).
  4210. '
  4211. ' NOTE: There is no 16-bit version of this function.
  4212. '
  4213. ' Returns: True on success, else False.
  4214. '   On success, lrefcount is set to the numeric data value
  4215. '
  4216. '----------------------------------------------------------
  4217. Function RegQueryRefCount(ByVal hKey As Long, ByVal strValueName As String, lRefCount As Long) As Boolean
  4218.     Dim lResult As Long
  4219.     Dim lValueType As Long
  4220.     Dim lBuf As Long
  4221.     Dim lDataBufSize As Long
  4222.  
  4223.     RegQueryRefCount = False
  4224.  
  4225.     On Error GoTo 0
  4226.  
  4227.     ' Get length/data type
  4228.     lDataBufSize = 4
  4229.  
  4230.     lResult = OSRegQueryValueEx(hKey, strValueName, 0&, lValueType, lBuf, lDataBufSize)
  4231.     If lResult = ERROR_SUCCESS Then
  4232.         Select Case lValueType
  4233.             Case REG_DWORD
  4234.                 lRefCount = lBuf
  4235.                 RegQueryRefCount = True
  4236.             Case REG_BINARY
  4237.                 If lDataBufSize = 4 Then
  4238.                     lRefCount = lBuf
  4239.                     RegQueryRefCount = True
  4240.                 End If
  4241.             Case REG_SZ
  4242.                 Dim strRefCount As String
  4243.                 
  4244.                 If RegQueryStringValue(hKey, strValueName, strRefCount) Then
  4245.                     lRefCount = Val(strRefCount)
  4246.                     RegQueryRefCount = True
  4247.                 End If
  4248.             'End Case
  4249.         End Select
  4250.     End If
  4251. End Function
  4252.  
  4253. ' FUNCTION: RegSetNumericValue
  4254. '
  4255. ' Associates a named (strValueName = name) or unnamed (strValueName = "")
  4256. '   value with a registry key.
  4257. '
  4258. ' If fLog is missing or is True, then this action is logged in the logfile,
  4259. ' and the value will be deleted by the application removal utility if the
  4260. ' user choose to remove the installed application.
  4261. '
  4262. ' NOTE: There is no 16-bit version of this function.
  4263. '
  4264. ' Returns: True on success, else False.
  4265. '
  4266. Function RegSetNumericValue(ByVal hKey As Long, ByVal strValueName As String, ByVal lData As Long, Optional ByVal fLog) As Boolean
  4267.     Dim lResult As Long
  4268.     Dim strHkey As String
  4269.  
  4270.     On Error GoTo 0
  4271.     
  4272.     If IsMissing(fLog) Then fLog = True
  4273.  
  4274.     strHkey = strGetHKEYString(hKey)
  4275.     
  4276.     If fLog Then
  4277.         NewAction _
  4278.           gstrKEY_REGVALUE, _
  4279.           """" & strHkey & """" _
  4280.             & ", " & """" & strValueName & """"
  4281.     End If
  4282.  
  4283.     lResult = OSRegSetValueEx(hKey, strValueName, 0&, REG_DWORD, lData, 4)
  4284.     If lResult = ERROR_SUCCESS Then
  4285.         RegSetNumericValue = True
  4286.         If fLog Then
  4287.             CommitAction
  4288.         End If
  4289.     Else
  4290.         RegSetNumericValue = False
  4291.         MsgError ResolveResString(resERR_REG), vbOKOnly Or vbExclamation, gstrTitle
  4292.         If fLog Then
  4293.             AbortAction
  4294.         End If
  4295.         If gfNoUserInput Then
  4296.             ExitSetup frmSetup1, gintRET_FATAL
  4297.         End If
  4298.     End If
  4299. End Function
  4300.  
  4301. ' FUNCTION: RegSetStringValue
  4302. '
  4303. ' Associates a named (strValueName = name) or unnamed (strValueName = "")
  4304. '   value with a registry key.
  4305. '
  4306. ' If fLog is missing or is True, then this action is logged in the
  4307. ' logfile, and the value will be deleted by the application removal
  4308. ' utility if the user choose to remove the installed application.
  4309. '
  4310. ' Returns: True on success, else False.
  4311. '
  4312. Function RegSetStringValue(ByVal hKey As Long, ByVal strValueName As String, ByVal strData As String, Optional ByVal fLog) As Boolean
  4313.     Dim lResult As Long
  4314.     Dim strHkey As String
  4315.     
  4316.     On Error GoTo 0
  4317.     
  4318.     If IsMissing(fLog) Then fLog = True
  4319.  
  4320.     If hKey = 0 Then
  4321.         Exit Function
  4322.     End If
  4323.     
  4324.     strHkey = strGetHKEYString(hKey)
  4325.  
  4326.     If fLog Then
  4327.         NewAction _
  4328.           gstrKEY_REGVALUE, _
  4329.           """" & strHkey & """" _
  4330.             & ", " & """" & strValueName & """"
  4331.     End If
  4332.  
  4333.     lResult = OSRegSetValueEx(hKey, strValueName, 0&, REG_SZ, ByVal strData, LenB(StrConv(strData, vbFromUnicode)) + 1)
  4334.     
  4335.     If lResult = ERROR_SUCCESS Then
  4336.         RegSetStringValue = True
  4337.         If fLog Then
  4338.             CommitAction
  4339.         End If
  4340.     Else
  4341.         RegSetStringValue = False
  4342.         MsgError ResolveResString(resERR_REG), vbOKOnly Or vbExclamation, gstrTitle
  4343.         If fLog Then
  4344.             AbortAction
  4345.         End If
  4346.         If gfNoUserInput Then
  4347.             ExitSetup frmSetup1, gintRET_FATAL
  4348.         End If
  4349.     End If
  4350. End Function
  4351.  
  4352. '-----------------------------------------------------------
  4353. ' SUB: RemoteRegister
  4354. '
  4355. ' Synchronously run the client registration utility on the
  4356. ' given remote server registration file in order to set it
  4357. ' up properly in the registry.
  4358. '
  4359. ' IN: [strFileName] - .EXE file to register
  4360.  
  4361. '-----------------------------------------------------------
  4362. '
  4363. Sub RemoteRegister(ByVal strFilename As String, rInfo As REGINFO)
  4364.     Const strClientRegistrationUtility$ = "CLIREG32.EXE"
  4365.     Const strAddressSwitch = " /s "
  4366.     Const strProtocolSwitch = " /p "
  4367.     Const strSilentSwitch = " /q "
  4368.     Const strNoLogoSwitch = " /nologo "
  4369.     Const strAuthenticationSwitch = " /a "
  4370.     Const strTypelibSwitch = " /t "
  4371.     Const strDCOMSwitch = " /d "
  4372.     Const strEXT_REMOTE$ = "VBR"
  4373.     Const strEXT_REMOTETLB$ = "TLB"
  4374.  
  4375.     Dim strAddress As String
  4376.     Dim strProtocol As String
  4377.     Dim intAuthentication As Integer
  4378.     Dim strCmdLine As String
  4379.     Dim fShell As Integer
  4380.     Dim strMatchingTLB As String
  4381.     Dim fDCOM As Boolean
  4382.  
  4383.     'Find the name of the matching typelib file.  This should have already
  4384.     'been installed to the same directory as the .VBR file.
  4385.     strMatchingTLB = strFilename
  4386.     If Right$(strMatchingTLB, Len(strEXT_REMOTE)) = strEXT_REMOTE Then
  4387.         strMatchingTLB = Left$(strMatchingTLB, Len(strMatchingTLB) - Len(strEXT_REMOTE))
  4388.     End If
  4389.     strMatchingTLB = strMatchingTLB & strEXT_REMOTETLB
  4390.  
  4391.     strAddress = rInfo.strNetworkAddress
  4392.     strProtocol = rInfo.strNetworkProtocol
  4393.     intAuthentication = rInfo.intAuthentication
  4394.     fDCOM = rInfo.fDCOM
  4395.     frmRemoteServerDetails.GetServerDetails strFilename, strAddress, strProtocol, fDCOM
  4396.     frmMessage.Refresh
  4397.     strCmdLine = _
  4398.       strClientRegistrationUtility _
  4399.       & strAddressSwitch & """" & strAddress & """" _
  4400.       & IIf(fDCOM, " ", strProtocolSwitch & strProtocol) _
  4401.       & IIf(fDCOM, " ", strAuthenticationSwitch & Format$(intAuthentication) & " ") _
  4402.       & strNoLogoSwitch _
  4403.       & strTypelibSwitch & """" & strMatchingTLB & """" & " " _
  4404.       & IIf(fDCOM, strDCOMSwitch, "") _
  4405.       & IIf(gfNoUserInput, strSilentSwitch, "") _
  4406.       & """" & strFilename & """"
  4407.       
  4408.     '
  4409.     'Synchronously shell out and run the utility with the correct switches
  4410.     '
  4411.     fShell = FSyncShell(strCmdLine, vbNormal)
  4412.  
  4413.     If Not fShell Then
  4414.         MsgError ResolveResString(resCANTRUNPROGRAM, "|1", strClientRegistrationUtility), vbOKOnly Or vbExclamation, gstrTitle, gintRET_FATAL
  4415.         ExitSetup frmSetup1, gintRET_FATAL
  4416.     End If
  4417. End Sub
  4418.  
  4419. '-----------------------------------------------------------
  4420. ' SUB: RemoveShellLink
  4421. '
  4422. ' Removes a link in either Start>Programs or any of its
  4423.  
  4424. ' immediate subfolders in the Windows 95 shell.
  4425. '
  4426. ' IN: [strFolderName] - text name of the immediate folder
  4427. '                       in which the link to be removed
  4428. '                       currently exists, or else the
  4429. '                       empty string ("") to indicate that
  4430. '                       the link can be found directly in
  4431. '                       the Start>Programs menu.
  4432. '     [strLinkName] - text caption for the link
  4433. '
  4434. ' This action is never logged in the app removal logfile.
  4435. '
  4436. ' PRECONDITION: strFolderName has already been created and is
  4437. '               an immediate subfolder of Start>Programs, if it
  4438. '               is not equal to ""
  4439. '-----------------------------------------------------------
  4440. '
  4441. Sub RemoveShellLink(ByVal strFolderName As String, ByVal strLinkName As String)
  4442.     Dim fSuccess As Boolean
  4443.     
  4444.     ReplaceDoubleQuotes strFolderName
  4445.     ReplaceDoubleQuotes strLinkName
  4446.     
  4447.     fSuccess = OSfRemoveShellLink(strFolderName, strLinkName)
  4448. End Sub
  4449.  
  4450. '-----------------------------------------------------------
  4451. ' FUNCTION: ResolveDestDir
  4452. '
  4453. ' Given a destination directory string, equate any macro
  4454. ' portions of the string to their runtime determined
  4455. ' actual locations and return a string reflecting the
  4456. ' actual path.
  4457. '
  4458. ' IN: [strDestDir] - string containing directory macro info
  4459. '                    and/or actual dir path info
  4460. '
  4461. '     [fAssumeDir] - boolean that if true, causes this routine
  4462. '                    to assume that strDestDir contains a dir
  4463. '                    path.  If a directory isn't given it will
  4464. '                    make it the application path.  If false,
  4465. '                    this routine will return strDestDir as
  4466. '                    is after performing expansion.  Set this
  4467. '                    to False when you are not sure it is a
  4468. '                    directory but you want to expand macros
  4469. '                    if it contains any.  E.g., If this is a
  4470. '                    command line parameter, you can't be
  4471. '                    certain if it refers to a path.  In this
  4472. '                    case, set fAssumeDir = False.  Default
  4473. '                    is True.
  4474. '
  4475. ' Return: A string containing the resolved dir name
  4476. '-----------------------------------------------------------
  4477. '
  4478. Function ResolveDestDir(ByVal strDestDir As String, Optional fAssumeDir As Variant) As String
  4479.     Const strMACROSTART$ = "$("
  4480.     Const strMACROEND$ = ")"
  4481.  
  4482.     Dim intPos As Integer
  4483.     Dim strResolved As String
  4484.     Dim hKey As Long
  4485.     Dim strPathsKey As String
  4486.     Dim fQuoted As Boolean
  4487.     
  4488.     If IsMissing(fAssumeDir) Then
  4489.         fAssumeDir = True
  4490.     End If
  4491.     
  4492.     strPathsKey = RegPathWinCurrentVersion()
  4493.     strDestDir = Trim(strDestDir)
  4494.     '
  4495.     ' If strDestDir is quoted when passed to this routine, it
  4496.     ' should be quoted when it's returned.  The quotes need
  4497.     ' to be temporarily removed, though, for processing.
  4498.     '
  4499.     If Left(strDestDir, 1) = gstrQUOTE Then
  4500.         fQuoted = True
  4501.         strDestDir = strUnQuoteString(strDestDir)
  4502.     End If
  4503.     '
  4504.     ' We take the first part of destdir, and if its $( then we need to get the portion
  4505.     ' of destdir up to and including the last paren.  We then test against this for
  4506.     ' macro expansion.  If no ) is found after finding $(, then must assume that it's
  4507.     ' just a normal file name and do no processing.  Only enter the case statement
  4508.     ' if strDestDir starts with $(.
  4509.     '
  4510.     If Left$(strDestDir, 2) = strMACROSTART Then
  4511.         intPos = InStr(strDestDir, strMACROEND)
  4512.  
  4513.         Select Case Left$(strDestDir, intPos)
  4514.             Case gstrAPPDEST
  4515.                 If gstrDestDir <> gstrNULL Then
  4516.  
  4517.                     strResolved = gstrDestDir
  4518.                 Else
  4519.                     strResolved = "?"
  4520.                 End If
  4521.             Case gstrWINDEST
  4522.                 strResolved = gstrWinDir
  4523.             Case gstrWINSYSDEST, gstrWINSYSDESTSYSFILE
  4524.                 strResolved = gstrWinSysDir
  4525.             Case gstrPROGRAMFILES
  4526.                 If TreatAsWin95() Then
  4527.                     Const strProgramFilesKey = "ProgramFilesDir"
  4528.     
  4529.                     If RegOpenKey(HKEY_LOCAL_MACHINE, strPathsKey, hKey) Then
  4530.                         RegQueryStringValue hKey, strProgramFilesKey, strResolved
  4531.                         RegCloseKey hKey
  4532.                     End If
  4533.                 End If
  4534.     
  4535.                 If strResolved = "" Then
  4536.                     'If not otherwise set, let strResolved be the root of the first fixed disk
  4537.                     strResolved = strRootDrive()
  4538.                 End If
  4539.             Case gstrCOMMONFILES
  4540.                 'First determine the correct path of Program Files\Common Files, if under Win95
  4541.                 strResolved = strGetCommonFilesPath()
  4542.                 If strResolved = "" Then
  4543.                     'If not otherwise set, let strResolved be the Windows directory
  4544.                     strResolved = gstrWinDir
  4545.                 End If
  4546.             Case gstrCOMMONFILESSYS
  4547.                 'First determine the correct path of Program Files\Common Files, if under Win95
  4548.                 Dim strCommonFiles As String
  4549.                 
  4550.                 strCommonFiles = strGetCommonFilesPath()
  4551.                 If strCommonFiles <> "" Then
  4552.                     'Okay, now just add \System, and we're done
  4553.                     strResolved = strCommonFiles & "System\"
  4554.                 Else
  4555.                     'If Common Files isn't in the registry, then map the
  4556.                     'entire macro to the Windows\{system,system32} directory
  4557.                     strResolved = gstrWinSysDir
  4558.                 End If
  4559.             Case gstrDAODEST
  4560.                 strResolved = strGetDAOPath()
  4561.             Case Else
  4562.                 intPos = 0
  4563.             'End Case
  4564.         End Select
  4565.     End If
  4566.     
  4567.     If intPos <> 0 Then
  4568.         AddDirSep strResolved
  4569.     End If
  4570.  
  4571.     If fAssumeDir = True Then
  4572.         If intPos = 0 Then
  4573.             '
  4574.             'if no drive spec, and doesn't begin with any root path indicator ("\"),
  4575.             'then we assume that this destination is relative to the app dest dir
  4576.             '
  4577.             If Mid$(strDestDir, 2, 1) <> gstrCOLON Then
  4578.                 If Left$(strDestDir, 1) <> gstrSEP_DIR Then
  4579.                     strResolved = gstrDestDir
  4580.                 End If
  4581.             End If
  4582.         Else
  4583.             If Mid$(strDestDir, intPos + 1, 1) = gstrSEP_DIR Then
  4584.                 intPos = intPos + 1
  4585.             End If
  4586.         End If
  4587.     End If
  4588.  
  4589.     If fQuoted = True Then
  4590.         ResolveDestDir = strQuoteString(strResolved & Mid$(strDestDir, intPos + 1), True, False)
  4591.     Else
  4592.         ResolveDestDir = strResolved & Mid$(strDestDir, intPos + 1)
  4593.     End If
  4594. End Function
  4595. '-----------------------------------------------------------
  4596. ' FUNCTION: ResolveDestDirs
  4597. '
  4598. ' Given a space delimited string, this routine finds all
  4599. ' Destination directory macros and expands them by making
  4600. ' repeated calls to ResolveDestDir.  See ResolveDestDir.
  4601. '
  4602. ' Note that the macro must immediately follow a space (or
  4603. ' a space followed by a quote) delimiter or else it will
  4604. ' be ignored.
  4605. '
  4606. ' Note that this routine does not assume that each item
  4607. ' in the delimited string is actually a directory path.
  4608. ' Therefore, the last parameter in the call to ResolveDestDir,
  4609. ' below, is false.
  4610. '
  4611. ' IN: [str] - string containing directory macro(s) info
  4612. '             and/or actual dir path info
  4613. '
  4614. ' Return: str with destdir macros expanded.
  4615. '-----------------------------------------------------------
  4616. '
  4617. Function ResolveDestDirs(str As String)
  4618.     Dim intAnchor As Integer
  4619.     Dim intOffset As Integer
  4620.     Dim strField As String
  4621.     Dim strExpField As String
  4622.     Dim strExpanded As String
  4623.     
  4624.     If Len(Trim(strUnQuoteString(str))) = 0 Then
  4625.         ResolveDestDirs = str
  4626.         Exit Function
  4627.     End If
  4628.         
  4629.     intAnchor = 1
  4630.     strExpanded = ""
  4631.     
  4632.     Do
  4633.         intOffset = intGetNextFldOffset(intAnchor, str, " ")
  4634.         If intOffset = 0 Then intOffset = Len(str) + 1
  4635.         strField = Mid(str, intAnchor, intOffset - intAnchor)
  4636.         strExpField = ResolveDestDir(strField, False)
  4637.         strExpanded = strExpanded & strExpField & " "
  4638.         intAnchor = intOffset + 1
  4639.     Loop While intAnchor < Len(str)
  4640.     
  4641.     ResolveDestDirs = Trim(strExpanded)
  4642. End Function
  4643. '-----------------------------------------------------------
  4644. ' FUNCTION: ResolveDir
  4645. '
  4646. ' Given a pathname, resolve it to its smallest form.  If
  4647. ' the pathname is invalid, then optionally warn the user.
  4648. '
  4649. ' IN: [strPathName] - pathname to resolve
  4650. '     [fMustExist] - enforce that the path actually exists
  4651. '     [fWarn] - If True, warn user upon invalid path
  4652. '
  4653. ' Return: A string containing the resolved dir name
  4654. '-----------------------------------------------------------
  4655. '
  4656. Function ResolveDir(ByVal strPathName As String, fMustExist As Integer, fWarn As Integer) As String
  4657.     Dim strMsg As String
  4658.     Dim fInValid As Integer
  4659.     Dim strUnResolvedPath As String
  4660.     Dim strResolvedPath As String
  4661.     Dim strIgnore As String
  4662.     Dim cbResolved As Long
  4663.  
  4664.     On Error Resume Next
  4665.  
  4666.     fInValid = False
  4667.     '
  4668.     'If the pathname is a UNC name (16-bit only), or if it's in actuality a file name, then it's invalid
  4669.     '
  4670.     If FileExists(strPathName) = True Then
  4671.         fInValid = True
  4672.         GoTo RDContinue
  4673.     End If
  4674.  
  4675.     strUnResolvedPath = strPathName
  4676.  
  4677.     If InStr(3, strUnResolvedPath, gstrSEP_DIR) > 0 Then
  4678.  
  4679.         strResolvedPath = Space(gintMAX_PATH_LEN * 2)
  4680.         cbResolved = GetFullPathName(strUnResolvedPath, gintMAX_PATH_LEN, strResolvedPath, strIgnore)
  4681.         If cbResolved = 0 Then
  4682.             '
  4683.             ' The path couldn't be resolved.  If we can actually
  4684.             ' switch to the directory we want, continue anyway.
  4685.             '
  4686.             ChDir strUnResolvedPath
  4687.             AddDirSep strUnResolvedPath
  4688.             If Err > 0 Then
  4689.                 Err = 0
  4690.                 ChDir strUnResolvedPath
  4691.                 If Err > 0 Then
  4692.                     fInValid = True
  4693.                 Else
  4694.                     strResolvedPath = strUnResolvedPath
  4695.                 End If
  4696.             Else
  4697.                 strResolvedPath = strUnResolvedPath
  4698.             End If
  4699.         Else
  4700.             '
  4701.             ' GetFullPathName returned us a NULL terminated string in
  4702.             ' strResolvedPath.  Remove the NULL.
  4703.             '
  4704.             strResolvedPath = StripTerminator(strResolvedPath)
  4705.             If CheckDrive(strResolvedPath, gstrTitle) = False Then
  4706.                 fInValid = True
  4707.             Else
  4708.                 AddDirSep strResolvedPath
  4709.                 If fMustExist = True Then
  4710.                     Err = 0
  4711.                     
  4712.                     Dim strDummy As String
  4713.                     strDummy = Dir$(strResolvedPath & "*.*")
  4714.                     
  4715.                     If Err > 0 Then
  4716.                         strMsg = ResolveResString(resNOTEXIST) & LS$
  4717.                         fInValid = True
  4718.                     End If
  4719.                 End If
  4720.             End If
  4721.         End If
  4722.     Else
  4723.         fInValid = True
  4724.     End If
  4725.  
  4726. RDContinue:
  4727.     If fInValid = True Then
  4728.         If fWarn = True Then
  4729.             strMsg = strMsg & ResolveResString(resDIRSPECIFIED) & LS$ & strPathName & LS$
  4730.             strMsg = strMsg & ResolveResString(resDIRINVALID)
  4731.             MsgError strMsg, MB_OK Or MB_ICONEXCLAMATION, ResolveResString(resDIRINVNAME)
  4732.             If gfNoUserInput Then
  4733.                 ExitSetup frmSetup1, gintRET_FATAL
  4734.             End If
  4735.         End If
  4736.  
  4737.         ResolveDir = gstrNULL
  4738.     Else
  4739.         ResolveDir = strResolvedPath
  4740.     End If
  4741.  
  4742.     Err = 0
  4743. End Function
  4744.  
  4745. '-----------------------------------------------------------
  4746. ' SUB: RestoreProgMan
  4747. '
  4748. ' Restores Windows Program Manager
  4749. '-----------------------------------------------------------
  4750. '
  4751. Sub RestoreProgMan()
  4752.     Const strPMTITLE$ = "Program Manager"
  4753.  
  4754.     On Error Resume Next
  4755.  
  4756.     'Try the localized name first
  4757.     AppActivate ResolveResString(resPROGRAMMANAGER)
  4758.     
  4759.     If Err Then
  4760.         'If that doesn't work, try the English name
  4761.         AppActivate strPMTITLE
  4762.     End If
  4763.  
  4764.     Err = 0
  4765. End Sub
  4766.  
  4767. '-----------------------------------------------------------
  4768. ' FUNCTION: SetFileDateTime
  4769. '
  4770. ' Set the Destination File's date and time to the Source file's date and time
  4771. '
  4772. ' IN: [strFileGetTime] - file to get time/date info from
  4773. '     [strFileSetTime] - file to set time/date info for
  4774. '
  4775. ' Returns: True if set date/time successful, False otherwise
  4776. '-----------------------------------------------------------
  4777. '
  4778. Function SetFileDateTime(strFileGetTime As String, strFileSetTime As String) As Integer
  4779.     SetFileDateTime = IIf(SetTime(strFileGetTime, strFileSetTime) = -1, False, True)
  4780. End Function
  4781.  
  4782. '-----------------------------------------------------------
  4783. ' SUB: ShowPathDialog
  4784. '
  4785. ' Display form to allow user to get either a source or
  4786. ' destination path
  4787. '
  4788. ' IN: [strPathRequest] - determines whether to ask for the
  4789. '                        source or destination pathname.
  4790. '                        gstrDIR_SRC for source path
  4791. '                        gstrDIR_DEST for destination path
  4792. '-----------------------------------------------------------
  4793. '
  4794. Sub ShowPathDialog(ByVal strPathRequest As String)
  4795.     frmSetup1.Tag = strPathRequest
  4796.  
  4797.     '
  4798.     'frmPath.Form_Load() reads frmSetup1.Tag to determine whether
  4799.     'this is a request for the source or destination path
  4800.     '
  4801.     frmPath.Show 1
  4802.  
  4803.     If strPathRequest = gstrDIR_SRC Then
  4804.         gstrSrcPath = frmSetup1.Tag
  4805.     Else
  4806.         If gfRetVal = gintRET_CONT Then
  4807.             gstrDestDir = frmSetup1.Tag
  4808.         End If
  4809.     End If
  4810. End Sub
  4811.  
  4812. '-----------------------------------------------------------
  4813. ' FUNCTION: strExtractFilenameArg
  4814. '
  4815. ' Extracts a quoted or unquoted filename from a string
  4816. '   containing command-line arguments
  4817. '
  4818. ' IN: [str] - string containing a filename.  This filename
  4819. '             begins at the first character, and continues
  4820. '             to the end of the string or to the first space
  4821. '             or switch character, or, if the string begins
  4822. '             with a double quote, continues until the next
  4823. '             double quote
  4824. ' OUT: Returns the filename, without quotes
  4825. '      str is set to be the remainder of the string after
  4826. '      the filename and quote (if any)
  4827. '
  4828. '-----------------------------------------------------------
  4829. '
  4830. Function strExtractFilenameArg(str As String, fErr As Boolean)
  4831.     Dim strFilename As String
  4832.     
  4833.     str = Trim$(str)
  4834.     
  4835.     Dim iEndFilenamePos As Integer
  4836.     If Left$(str, 1) = """" Then
  4837.         ' Filenames is surrounded by quotes
  4838.         iEndFilenamePos = InStr(2, str, """") ' Find matching quote
  4839.         If iEndFilenamePos > 0 Then
  4840.             strFilename = Mid$(str, 2, iEndFilenamePos - 2)
  4841.             str = Right$(str, Len(str) - iEndFilenamePos)
  4842.         Else
  4843.             fErr = True
  4844.             Exit Function
  4845.         End If
  4846.     Else
  4847.         ' Filename continues until next switch or space or quote
  4848.         Dim iSpacePos As Integer
  4849.         Dim iSwitch1 As Integer
  4850.         Dim iSwitch2 As Integer
  4851.         Dim iQuote As Integer
  4852.         
  4853.         iSpacePos = InStr(str, " ")
  4854.         iSwitch1 = InStr(str, gstrSwitchPrefix1)
  4855.         iSwitch2 = InStr(str, gstrSwitchPrefix2)
  4856.         iQuote = InStr(str, """")
  4857.         
  4858.         If iSpacePos = 0 Then iSpacePos = Len(str) + 1
  4859.         If iSwitch1 = 0 Then iSwitch1 = Len(str) + 1
  4860.         If iSwitch2 = 0 Then iSwitch2 = Len(str) + 1
  4861.         If iQuote = 0 Then iQuote = Len(str) + 1
  4862.         
  4863.         iEndFilenamePos = iSpacePos
  4864.         If iSwitch1 < iEndFilenamePos Then iEndFilenamePos = iSwitch1
  4865.         If iSwitch2 < iEndFilenamePos Then iEndFilenamePos = iSwitch2
  4866.         If iQuote < iEndFilenamePos Then iEndFilenamePos = iQuote
  4867.         
  4868.         strFilename = Left$(str, iEndFilenamePos - 1)
  4869.         If iEndFilenamePos > Len(str) Then
  4870.             str = ""
  4871.         Else
  4872.             str = Right(str, Len(str) - iEndFilenamePos + 1)
  4873.         End If
  4874.     End If
  4875.     
  4876.     strFilename = Trim$(strFilename)
  4877.     If strFilename = "" Then
  4878.         fErr = True
  4879.         Exit Function
  4880.     End If
  4881.     
  4882.     fErr = False
  4883.     strExtractFilenameArg = strFilename
  4884.     str = Trim$(str)
  4885. End Function
  4886.  
  4887.  
  4888.  
  4889. '-----------------------------------------------------------
  4890. ' SUB: UpdateStatus
  4891. '
  4892. ' "Fill" (by percentage) inside the PictureBox and also
  4893. ' display the percentage filled
  4894. '
  4895. ' IN: [pic] - PictureBox used to bound "fill" region
  4896. '     [sngPercent] - Percentage of the shape to fill
  4897. '     [fBorderCase] - Indicates whether the percentage
  4898. '        specified is a "border case", i.e. exactly 0%
  4899. '        or exactly 100%.  Unless fBorderCase is True,
  4900. '        the values 0% and 100% will be assumed to be
  4901. '        "close" to these values, and 1% and 99% will
  4902. '        be used instead.
  4903. '
  4904. ' Notes: Set AutoRedraw property of the PictureBox to True
  4905. '        so that the status bar and percentage can be auto-
  4906. '        matically repainted if necessary
  4907. '-----------------------------------------------------------
  4908. '
  4909. Sub UpdateStatus(pic As PictureBox, ByVal sngPercent As Single, Optional ByVal fBorderCase)
  4910.     Dim strPercent As String
  4911.     Dim intX As Integer
  4912.     Dim intY As Integer
  4913.     Dim intWidth As Integer
  4914.     Dim intHeight As Integer
  4915.  
  4916.     If IsMissing(fBorderCase) Then fBorderCase = False
  4917.     
  4918.     'For this to work well, we need a white background and any color foreground (blue)
  4919.     Const colBackground = &HFFFFFF ' white
  4920.     Const colForeground = &H800000 ' dark blue
  4921.  
  4922.     pic.ForeColor = colForeground
  4923.     pic.BackColor = colBackground
  4924.     
  4925.     '
  4926.     'Format percentage and get attributes of text
  4927.     '
  4928.     Dim intPercent
  4929.     intPercent = Int(100 * sngPercent + 0.5)
  4930.     
  4931.     'Never allow the percentage to be 0 or 100 unless it is exactly that value.  This
  4932.     'prevents, for instance, the status bar from reaching 100% until we are entirely done.
  4933.     If intPercent = 0 Then
  4934.         If Not fBorderCase Then
  4935.             intPercent = 1
  4936.         End If
  4937.     ElseIf intPercent = 100 Then
  4938.         If Not fBorderCase Then
  4939.             intPercent = 99
  4940.         End If
  4941.     End If
  4942.     
  4943.     strPercent = Format$(intPercent) & "%"
  4944.     intWidth = pic.TextWidth(strPercent)
  4945.     intHeight = pic.TextHeight(strPercent)
  4946.  
  4947.     '
  4948.     'Now set intX and intY to the starting location for printing the percentage
  4949.     '
  4950.     intX = pic.Width / 2 - intWidth / 2
  4951.     intY = pic.Height / 2 - intHeight / 2
  4952.  
  4953.     '
  4954.     'Need to draw a filled box with the pics background color to wipe out previous
  4955.     'percentage display (if any)
  4956.     '
  4957.     pic.DrawMode = 13 ' Copy Pen
  4958.     pic.Line (intX, intY)-Step(intWidth, intHeight), pic.BackColor, BF
  4959.  
  4960.     '
  4961.     'Back to the center print position and print the text
  4962.     '
  4963.     pic.CurrentX = intX
  4964.     pic.CurrentY = intY
  4965.     pic.Print strPercent
  4966.  
  4967.     '
  4968.     'Now fill in the box with the ribbon color to the desired percentage
  4969.     'If percentage is 0, fill the whole box with the background color to clear it
  4970.     'Use the "Not XOR" pen so that we change the color of the text to white
  4971.     'wherever we touch it, and change the color of the background to blue
  4972.     'wherever we touch it.
  4973.     '
  4974.     pic.DrawMode = 10 ' Not XOR Pen
  4975.     If sngPercent > 0 Then
  4976.         pic.Line (0, 0)-(pic.Width * sngPercent, pic.Height), pic.ForeColor, BF
  4977.     Else
  4978.         pic.Line (0, 0)-(pic.Width, pic.Height), pic.BackColor, BF
  4979.     End If
  4980.  
  4981.     pic.Refresh
  4982. End Sub
  4983.  
  4984. '-----------------------------------------------------------
  4985. ' FUNCTION: WriteAccess
  4986. '
  4987. ' Determines whether there is write access to the specified
  4988. ' directory.
  4989. '
  4990. ' IN: [strDirName] - directory to check for write access
  4991. '
  4992. ' Returns: True if write access, False otherwise
  4993. '-----------------------------------------------------------
  4994. '
  4995. Function WriteAccess(ByVal strDirName As String) As Integer
  4996.     Dim intFileNum As Integer
  4997.  
  4998.     On Error Resume Next
  4999.  
  5000.     AddDirSep strDirName
  5001.  
  5002.     intFileNum = FreeFile
  5003.     Open strDirName & mstrCONCATFILE For Output As intFileNum
  5004.  
  5005.     WriteAccess = IIf(Err, False, True)
  5006.     
  5007.     Close intFileNum
  5008.  
  5009.     Kill strDirName & mstrCONCATFILE
  5010.  
  5011.     Err = 0
  5012. End Function
  5013. '-----------------------------------------------------------
  5014. ' FUNCTION: WriteMIF
  5015. '
  5016. ' If this is a SMS install, this routine writes the
  5017. ' failed MIF status file if something goes wrong or
  5018. ' a successful MIF if everything installs correctly.
  5019. '
  5020. ' The MIF file requires a special format specified
  5021. ' by SMS.  Currently, this routine implements the
  5022. ' minimum requirements.  The hardcoded strings below
  5023. ' that are written to the MIF should be written
  5024. ' character by character as they are; except that
  5025. ' status message should change depending on the
  5026. ' circumstances of the install.  DO NOT LOCALIZE
  5027. ' anything except the status message.
  5028. '
  5029. ' IN: [strMIFFilename] - The name of the MIF file.
  5030. '                        Passed in to setup1 by
  5031. '                        setup.exe.  It is probably
  5032. '                        named <appname>.mif where
  5033. '                        <appname> is the name of the
  5034. '                        application you are installing.
  5035. '
  5036. '     [fStatus] - False to write a failed MIF (i.e. setup
  5037. '                 failed); True to write a successful MIF.
  5038. '
  5039. '     [strSMSDescription] - This is the description string
  5040. '                           to be written to the MIF file.
  5041. '                           It cannot be longer than 255
  5042. '                           characters and cannot contain
  5043. '                           carriage returns and/or line
  5044. '                           feeds.  This routine will
  5045. '                           enforce these requirements.
  5046. '
  5047. ' Note, when running in SMS mode, there is no other way
  5048. ' to display a message to the user than to write it to
  5049. ' the MIF file.  Displaying a MsgBox will cause the
  5050. ' computer to appear as if it has hung.  Therefore, this
  5051. ' routine makes no attempt to display an error message.
  5052. '
  5053. '-----------------------------------------------------------
  5054. '
  5055. Sub WriteMIF(ByVal strMIFFilename As String, ByVal fStatus As Boolean, ByVal strSMSDescription As String)
  5056.     Const strSUCCESS = """SUCCESS"""                 ' Cannot be localized as per SMS
  5057.     Const strFAILED = """FAILED"""                   ' Cannot be localized as per SMS
  5058.     
  5059.     Dim fn As Integer
  5060.     Dim intOffset As Integer
  5061.     Dim fOpened As Boolean
  5062.         
  5063.     fOpened = False
  5064.         
  5065.     On Error GoTo WMIFFAILED  ' If we fail, we just return without doing anything
  5066.                               ' because there is no way to inform the user while
  5067.                               ' in SMS mode.
  5068.  
  5069.     '
  5070.     ' If the description string is greater than 255 characters,
  5071.     ' truncate it.  Required my SMS.
  5072.     '
  5073.     strSMSDescription = Left(strSMSDescription, MAX_SMS_DESCRIP)
  5074.     '
  5075.     ' Remove any carriage returns or line feeds and replace
  5076.     ' them with spaces.  The message must be a single line.
  5077.     '
  5078.     For intOffset = 1 To Len(strSMSDescription)
  5079.         If (Mid(strSMSDescription, intOffset, 1) = Chr(10)) Or (Mid(strSMSDescription, intOffset, 1) = Chr(13)) Then
  5080.             Mid(strSMSDescription, intOffset, 1) = " "
  5081.         End If
  5082.     Next intOffset
  5083.     '
  5084.     ' Open the MIF file for append, but first delete any existing
  5085.     ' ones with the same name.  Note, that setup.exe passed a
  5086.     ' unique name so if there is one with this name already in
  5087.     ' on the disk, it was put there by setup.exe.
  5088.     '
  5089.     If FileExists(strMIFFilename) Then
  5090.         Kill strMIFFilename
  5091.     End If
  5092.     
  5093.     fn = FreeFile
  5094.     Open strMIFFilename For Append As fn
  5095.     fOpened = True
  5096.     '
  5097.     ' We are ready to write the actual MIF file
  5098.     ' Note, none of the string below are supposed
  5099.     ' to be localized.
  5100.     '
  5101.     Print #fn, "Start Component"
  5102.         Print #fn, Tab; "Name = ""Workstation"""
  5103.         Print #fn, Tab; "Start Group"
  5104.             Print #fn, Tab; Tab; "Name = ""InstallStatus"""
  5105.             Print #fn, Tab; Tab; "ID = 1"
  5106.             Print #fn, Tab; Tab; "Class = ""MICROSOFT|JOBSTATUS|1.0"""
  5107.             Print #fn, Tab; Tab; "Start Attribute"
  5108.                 Print #fn, Tab; Tab; Tab; "Name = ""Status"""
  5109.                 Print #fn, Tab; Tab; Tab; "ID = 1"
  5110.                 Print #fn, Tab; Tab; Tab; "Type = String(16)"
  5111.                 Print #fn, Tab; Tab; Tab; "Value = "; IIf(fStatus, strSUCCESS, strFAILED)
  5112.             Print #fn, Tab; Tab; "End Attribute"
  5113.             Print #fn, Tab; Tab; "Start Attribute"
  5114.                 Print #fn, Tab; Tab; Tab; "Name = ""Description"""
  5115.                 Print #fn, Tab; Tab; Tab; "ID = 2"
  5116.                 Print #fn, Tab; Tab; Tab; "Type = String(256)"
  5117.                 Print #fn, Tab; Tab; Tab; "Value = "; strSMSDescription
  5118.             Print #fn, Tab; Tab; "End Attribute"
  5119.         Print #fn, Tab; "End Group"
  5120.     Print #fn, "End Component"
  5121.  
  5122.     Close fn
  5123.     '
  5124.     ' Success
  5125.     '
  5126.     Exit Sub
  5127.  
  5128. WMIFFAILED:
  5129.     '
  5130.     ' At this point we are unable to create the MIF file.
  5131.     ' Since we are running under SMS there is no one to
  5132.     ' tell, so we don't generate an error message at all.
  5133.     '
  5134.     If fOpened = True Then
  5135.         Close fn
  5136.     End If
  5137.     Exit Sub
  5138. End Sub
  5139.  
  5140. 'Adds or replaces an HKEY to the list of HKEYs in cache.
  5141. 'Note that it is not necessary to remove keys from
  5142. 'this list.
  5143. Private Sub AddHkeyToCache(ByVal hKey As Long, ByVal strHkey As String)
  5144.     Dim intIdx As Integer
  5145.     
  5146.     intIdx = intGetHKEYIndex(hKey)
  5147.     If intIdx < 0 Then
  5148.         'The key does not already exist.  Add it to the end.
  5149.         On Error Resume Next
  5150.         ReDim Preserve hkeyCache(0 To UBound(hkeyCache) + 1)
  5151.         If Err Then
  5152.             'If there was an error, it means the cache was empty.
  5153.             On Error GoTo 0
  5154.             ReDim hkeyCache(0 To 0)
  5155.         End If
  5156.         On Error GoTo 0
  5157.  
  5158.         intIdx = UBound(hkeyCache)
  5159.     Else
  5160.         'The key already exists.  It will be replaced.
  5161.     End If
  5162.  
  5163.     hkeyCache(intIdx).hKey = hKey
  5164.     hkeyCache(intIdx).strHkey = strHkey
  5165. End Sub
  5166.  
  5167. 'Given a predefined HKEY, return the text string representing that
  5168. 'key, or else return "".
  5169. Private Function strGetPredefinedHKEYString(ByVal hKey As Long) As String
  5170.     Select Case hKey
  5171.         Case HKEY_CLASSES_ROOT
  5172.             strGetPredefinedHKEYString = "HKEY_CLASSES_ROOT"
  5173.         Case HKEY_CURRENT_USER
  5174.             strGetPredefinedHKEYString = "HKEY_CURRENT_USER"
  5175.         Case HKEY_LOCAL_MACHINE
  5176.             strGetPredefinedHKEYString = "HKEY_LOCAL_MACHINE"
  5177.         Case HKEY_USERS
  5178.             strGetPredefinedHKEYString = "HKEY_USERS"
  5179.         'End Case
  5180.     End Select
  5181. End Function
  5182.  
  5183. 'Given an HKEY, return the text string representing that
  5184. 'key.
  5185. Private Function strGetHKEYString(ByVal hKey As Long) As String
  5186.     Dim strKey As String
  5187.  
  5188.     'Is the hkey predefined?
  5189.     strKey = strGetPredefinedHKEYString(hKey)
  5190.     If strKey <> "" Then
  5191.         strGetHKEYString = strKey
  5192.         Exit Function
  5193.     End If
  5194.     
  5195.     'It is not predefined.  Look in the cache.
  5196.     Dim intIdx As Integer
  5197.     intIdx = intGetHKEYIndex(hKey)
  5198.     If intIdx >= 0 Then
  5199.         strGetHKEYString = hkeyCache(intIdx).strHkey
  5200.     Else
  5201.         strGetHKEYString = ""
  5202.     End If
  5203. End Function
  5204.  
  5205. 'Searches the cache for the index of the given HKEY.
  5206. 'Returns the index if found, else returns -1.
  5207. Private Function intGetHKEYIndex(ByVal hKey As Long) As Integer
  5208.     Dim intUBound As Integer
  5209.     
  5210.     On Error Resume Next
  5211.     intUBound = UBound(hkeyCache)
  5212.     If Err Then
  5213.         'If there was an error accessing the ubound of the array,
  5214.         'then the cache is empty
  5215.         GoTo NotFound
  5216.     End If
  5217.     On Error GoTo 0
  5218.  
  5219.     Dim intIdx As Integer
  5220.     For intIdx = 0 To intUBound
  5221.         If hkeyCache(intIdx).hKey = hKey Then
  5222.             intGetHKEYIndex = intIdx
  5223.             Exit Function
  5224.         End If
  5225.     Next intIdx
  5226.     
  5227. NotFound:
  5228.     intGetHKEYIndex = -1
  5229. End Function
  5230.  
  5231. 'Returns the location of the Program Files\Common Files path, if
  5232. 'it is present in the registry.  Otherwise, returns "".
  5233. Public Function strGetCommonFilesPath() As String
  5234.     Dim hKey As Long
  5235.     Dim strPath As String
  5236.     
  5237.     If TreatAsWin95() Then
  5238.         Const strCommonFilesKey = "CommonFilesDir"
  5239.  
  5240.         If RegOpenKey(HKEY_LOCAL_MACHINE, RegPathWinCurrentVersion(), hKey) Then
  5241.             RegQueryStringValue hKey, strCommonFilesKey, strPath
  5242.             RegCloseKey hKey
  5243.         End If
  5244.     End If
  5245.  
  5246.     If strPath <> "" Then
  5247.         AddDirSep strPath
  5248.     End If
  5249.     
  5250.     strGetCommonFilesPath = strPath
  5251. End Function
  5252. 'Returns the location of the "Windows\Start Menu\Programs" Files path, if
  5253. 'it is present in the registry.  Otherwise, returns "".
  5254. Public Function strGetProgramsFilesPath() As String
  5255.     Dim hKey As Long
  5256.     Dim strPath As String
  5257.     
  5258.     strPath = ""
  5259.     If TreatAsWin95() Then
  5260.         Const strProgramsKey = "Programs"
  5261.  
  5262.         If RegOpenKey(HKEY_CURRENT_USER, RegPathWinPrograms(), hKey) Then
  5263.             RegQueryStringValue hKey, strProgramsKey, strPath
  5264.             RegCloseKey hKey
  5265.         End If
  5266.     End If
  5267.  
  5268.     If strPath <> "" Then
  5269.         AddDirSep strPath
  5270.     End If
  5271.     
  5272.     strGetProgramsFilesPath = strPath
  5273. End Function
  5274.  
  5275. 'Returns the directory where DAO is or should be installed.  If the
  5276. 'key does not exist in the registry, it is created.  For instance, under
  5277. 'NT 3.51 this location is normally 'C:\WINDOWS\MSAPPS\DAO'
  5278. Private Function strGetDAOPath() As String
  5279.     Const strMSAPPS$ = "MSAPPS\"
  5280.     Const strDAO3032$ = "DAO3032.DLL"
  5281.     
  5282.     'first look in the registry
  5283.     Const strKey = "SOFTWARE\Microsoft\Shared Tools\DAO"
  5284.     Const strValueName = "Path"
  5285.     Dim hKey As Long
  5286.     Dim strPath As String
  5287.  
  5288.     If RegOpenKey(HKEY_LOCAL_MACHINE, strKey, hKey) Then
  5289.         RegQueryStringValue hKey, strValueName, strPath
  5290.         RegCloseKey hKey
  5291.     End If
  5292.  
  5293.     If strPath <> "" Then
  5294.         strPath = GetPathName(strPath)
  5295.         AddDirSep strPath
  5296.         strGetDAOPath = strPath
  5297.         Exit Function
  5298.     End If
  5299.     
  5300.     'It's not yet in the registry, so we need to decide
  5301.     'where the directory should be, and then need to place
  5302.     'that location in the registry.
  5303.  
  5304.     If TreatAsWin95() Then
  5305.         'For Win95, use "Common Files\Microsoft Shared\DAO"
  5306.         strPath = strGetCommonFilesPath() & ResolveResString(resMICROSOFTSHARED) & "DAO\"
  5307.     Else
  5308.         'Otherwise use Windows\MSAPPS\DAO
  5309.         strPath = gstrWinDir & strMSAPPS & "DAO\"
  5310.     End If
  5311.     
  5312.     'Place this information in the registry (note that we point to DAO3032.DLL
  5313.     'itself, not just to the directory)
  5314.     If RegCreateKey(HKEY_LOCAL_MACHINE, strKey, "", hKey) Then
  5315.         RegSetStringValue hKey, strValueName, strPath & strDAO3032, False
  5316.         RegCloseKey hKey
  5317.     End If
  5318.  
  5319.     strGetDAOPath = strPath
  5320. End Function
  5321.  
  5322. ' Replace all double quotes with single quotes
  5323. Public Sub ReplaceDoubleQuotes(str As String)
  5324.     Dim i As Integer
  5325.     
  5326.     For i = 1 To Len(str)
  5327.         If Mid$(str, i, 1) = """" Then
  5328.             Mid$(str, i, 1) = "'"
  5329.         End If
  5330.     Next i
  5331. End Sub
  5332.  
  5333. 'Get the path portion of a filename
  5334. Function GetPathName(ByVal strFilename As String) As String
  5335.     Dim intPos As Integer
  5336.     Dim strPathOnly As String
  5337.     Dim dirTmp As DirListBox
  5338.     Dim i As Integer
  5339.  
  5340.     On Error Resume Next
  5341.  
  5342.  
  5343.     Err = 0
  5344.     
  5345.     intPos = Len(strFilename)
  5346.  
  5347.     '
  5348.     'Change all '/' chars to '\'
  5349.     '
  5350.  
  5351.     For i = 1 To Len(strFilename)
  5352.         If Mid$(strFilename, i, 1) = gstrSEP_DIRALT Then
  5353.             Mid$(strFilename, i, 1) = gstrSEP_DIR
  5354.         End If
  5355.     Next i
  5356.  
  5357.     If InStr(strFilename, gstrSEP_DIR) = intPos Then
  5358.         If intPos > 1 Then
  5359.             intPos = intPos - 1
  5360.         End If
  5361.     Else
  5362.         Do While intPos > 0
  5363.             If Mid$(strFilename, intPos, 1) <> gstrSEP_DIR Then
  5364.                 intPos = intPos - 1
  5365.             Else
  5366.                 Exit Do
  5367.             End If
  5368.         Loop
  5369.     End If
  5370.  
  5371.     If intPos > 0 Then
  5372.         strPathOnly = Left$(strFilename, intPos)
  5373.         If Right$(strPathOnly, 1) = gstrCOLON Then
  5374.             strPathOnly = strPathOnly & gstrSEP_DIR
  5375.         End If
  5376.     Else
  5377.         strPathOnly = CurDir$
  5378.     End If
  5379.  
  5380.     If Right$(strPathOnly, 1) = gstrSEP_DIR Then
  5381.         strPathOnly = Left$(strPathOnly, Len(strPathOnly) - 1)
  5382.     End If
  5383.  
  5384.     GetPathName = UCase16(strPathOnly)
  5385.     
  5386.     Err = 0
  5387. End Function
  5388.  
  5389. 'Returns the path to the root of the first fixed disk
  5390. Function strRootDrive() As String
  5391.     Dim intDriveNum As Integer
  5392.     
  5393.     For intDriveNum = 0 To Asc("Z") - Asc("A") - 1
  5394.         If GetDriveType(intDriveNum) = intDRIVE_FIXED Then
  5395.             strRootDrive = Chr$(Asc("A") + intDriveNum) & gstrCOLON & gstrSEP_DIR
  5396.             Exit Function
  5397.         End If
  5398.     Next intDriveNum
  5399.     
  5400.     strRootDrive = "C:\"
  5401. End Function
  5402.  
  5403. 'Returns "" if the path is not complete, or is a UNC pathname
  5404. Function strGetDriveFromPath(ByVal strPath As String) As String
  5405.     If Len(strPath) < 2 Then
  5406.         Exit Function
  5407.     End If
  5408.     
  5409.     If Mid$(strPath, 2, 1) <> gstrCOLON Then
  5410.         Exit Function
  5411.     End If
  5412.     
  5413.     strGetDriveFromPath = Mid$(strPath, 1, 1) & gstrCOLON & gstrSEP_DIR
  5414. End Function
  5415.  
  5416. Public Function fValidFilename(strFilename As String) As Boolean
  5417. '
  5418. ' This routine verifies that strFileName is a valid file name.
  5419. ' It checks that its length is less than the max allowed
  5420. ' and that it doesn't contain any invalid characters..
  5421. '
  5422.     If Not fCheckFNLength(strFilename) Then
  5423.         '
  5424.         ' Name is too long.
  5425.         '
  5426.         fValidFilename = False
  5427.         Exit Function
  5428.     End If
  5429.     '
  5430.     ' Search through the list of invalid filename characters and make
  5431.     ' sure none of them are in the string.
  5432.     '
  5433.     Dim iInvalidChar As Integer
  5434.     Dim iFilename As Integer
  5435.     Dim strInvalidChars As String
  5436.     
  5437.     strInvalidChars = ResolveResString(resCOMMON_INVALIDFILECHARS)
  5438.     
  5439.     For iInvalidChar = 1 To Len(strInvalidChars)
  5440.         If InStr(strFilename, Mid$(strInvalidChars, iInvalidChar, 1)) <> 0 Then
  5441.             fValidFilename = False
  5442.             Exit Function
  5443.         End If
  5444.     Next iInvalidChar
  5445.     
  5446.     fValidFilename = True
  5447.     
  5448. End Function
  5449. Public Function fValidNTGroupName(strGroupName) As Boolean
  5450. '
  5451. ' This routine verifies that strGroupName is a valid group name.
  5452. ' It checks that its length is less than the max allowed
  5453. ' and that it doesn't contain any invalid characters.
  5454. '
  5455.     If Len(strGroupName) > gintMAX_GROUPNAME_LEN Then
  5456.         fValidNTGroupName = False
  5457.         Exit Function
  5458.     End If
  5459.     '
  5460.     ' Search through the list of invalid filename characters and make
  5461.     ' sure none of them are in the string.
  5462.     '
  5463.     Dim iInvalidChar As Integer
  5464.     Dim iFilename As Integer
  5465.     Dim strInvalidChars As String
  5466.     
  5467.     strInvalidChars = ResolveResString(resGROUPINVALIDCHARS)
  5468.     
  5469.     For iInvalidChar = 1 To Len(strInvalidChars)
  5470.         If InStr(strGroupName, Mid$(strInvalidChars, iInvalidChar, 1)) <> 0 Then
  5471.             fValidNTGroupName = False
  5472.             Exit Function
  5473.         End If
  5474.     Next iInvalidChar
  5475.     
  5476.     fValidNTGroupName = True
  5477.     
  5478. End Function
  5479. '-----------------------------------------------------------
  5480. ' SUB: CountIcons
  5481. '
  5482. ' Determines how many icons must be installed by counting
  5483. ' them in the setup information file (SETUP.LST)
  5484. '-----------------------------------------------------------
  5485. '
  5486. Function CountIcons(ByVal strSection As String) As Integer
  5487.     Dim intIdx As Integer
  5488.     Dim sFile As FILEINFO
  5489.     Dim cIcons As Integer
  5490.  
  5491.     '
  5492.     'For each file in the specified section, read info from the setup info file
  5493.     '
  5494.     intIdx = 1
  5495.     cIcons = 0
  5496.     Do While ReadSetupFileLine(strSection, intIdx, sFile) = True
  5497.         If Len(strUnQuoteString(sFile.strProgramIconTitle)) > 0 Then
  5498.             cIcons = cIcons + 1
  5499.         End If
  5500.         intIdx = intIdx + 1
  5501.     Loop
  5502.     CountIcons = cIcons
  5503. End Function
  5504. '-----------------------------------------------------------
  5505. ' SUB: CreateIcons
  5506. '
  5507. ' Walks through the list of files in SETUP.LST and creates
  5508. ' Icons in the Program Group for files needed it.
  5509. '-----------------------------------------------------------
  5510. '
  5511. Sub CreateIcons(ByVal strSection As String, ByVal strGroupName As String)
  5512.     Dim intIdx As Integer
  5513.     Dim sFile As FILEINFO
  5514.     Dim strProgramIconTitle As String
  5515.     Dim strProgramIconCmdLine As String
  5516.     Dim strProgramPath As String
  5517.     Dim strProgramArgs As String
  5518.     Dim intAnchor As Integer
  5519.     Dim intOffset As Integer
  5520.     Const CompareBinary = 0
  5521.     '
  5522.     'For each file in the specified section, read info from the setup info file
  5523.     '
  5524.     intIdx = 1
  5525.     Do While ReadSetupFileLine(strSection, intIdx, sFile) = True
  5526.         '
  5527.         ' Get the Icon's caption and command line
  5528.         '
  5529.         strProgramIconTitle = sFile.strProgramIconTitle
  5530.         strProgramIconCmdLine = sFile.strProgramIconCmdLine
  5531.         '
  5532.         ' if the ProgramIcon is specified, then we create an icon,
  5533.         ' otherwise we don't.
  5534.         '
  5535.         If Trim(strUnQuoteString(strProgramIconTitle)) <> "" Then
  5536.             '
  5537.             ' If the command line is not specified in SETUP.LST and the icon
  5538.             ' is, then use the files destination path as the command line.  In
  5539.             ' this case there are no parameters.
  5540.             '
  5541.             If Trim(strUnQuoteString(strProgramIconCmdLine)) = "" Then
  5542.                 strProgramPath = sFile.strDestDir & gstrSEP_DIR & sFile.strDestName
  5543.                 strProgramArgs = ""
  5544.             Else
  5545.                 '
  5546.                 ' Parse the command line, to determine what is the exe, etc. and what
  5547.                 ' are the parameters.  The first space that is not contained within
  5548.                 ' quotes, marks the end of the exe, etc..  Everything afterwards are
  5549.                 ' parameters/arguments for the exe.  NOTE: It is important that if
  5550.                 ' the exe is contained within quotes that the parameters not be
  5551.                 ' contained within the same quotes.  The arguments can themselves
  5552.                 ' each be inside quotes as long as they are not in the same quotes
  5553.                 ' with the exe.
  5554.                 '
  5555.                 intAnchor = 1
  5556.                 intOffset = intGetNextFldOffset(intAnchor, strProgramIconCmdLine, " ", CompareBinary)
  5557.                 If intOffset = 0 Then intOffset = Len(strProgramIconCmdLine) + 1
  5558.                 strProgramPath = Trim(Left(strProgramIconCmdLine, intOffset - 1))
  5559.                 '
  5560.                 ' Got the exe, now the parameters.
  5561.                 '
  5562.                 strProgramArgs = Trim(Mid(strProgramIconCmdLine, intOffset + 1))
  5563.             End If
  5564.             '
  5565.             ' Expand all the Destination Directory macros that are embedded in the
  5566.             ' Program Path and the Arguments'
  5567.             '
  5568.             strProgramPath = ResolveDestDir(strProgramPath)
  5569.             strProgramArgs = ResolveDestDirs(strProgramArgs)
  5570.             '
  5571.             ' Finally, we have everything we need, create the icon.
  5572.             '
  5573.             CreateOSLink frmSetup1, strGroupName, strProgramPath, strProgramArgs, strProgramIconTitle
  5574.         ElseIf Trim(strUnQuoteString(strProgramIconCmdLine)) <> "" Then
  5575.             '
  5576.             ' This file contained specified a command line in SETUP.LST but no icon.
  5577.             ' This is an error.  Let the user know and skip this icon or abort.
  5578.  
  5579.             '
  5580.             If gfNoUserInput Or MsgWarning(ResolveResString(resICONMISSING, "|1", sFile.strDestName), vbYesNo Or vbExclamation, gstrSETMSG) = vbNo Then
  5581.                 ExitSetup frmSetup1, gintRET_FATAL
  5582.             End If
  5583.         End If
  5584.         intIdx = intIdx + 1
  5585.     Loop
  5586. End Sub
  5587.  
  5588.